How do I use SwingWorker in Java?
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.
How to use SwingWorker?
At first, i just call the doInBackground() function from MySQL.execute(); using this. And then in doInBackground() function , i just collect those counter values and use publish(); function to passcertain value. here i just pass flag to denote data's were collected successfully.
publish(GraphLock);
After calling the Publish(); method, Process(List chunks) method get invoked. On that i just check the condition and call the Graph class to generate the graph.
if(GraphLock==true)
SwingUtilities.invokeLater(new Graph());
It works properly...
How to use SwingWorker to update the GUI in real-time?
your background threads should call method
String message="hi im processx";
SwingWorker.publish(message);
and you have to ovveride method
SwingWokrer.process().
this method is called on EDT when it is free with the message which was passed before to publish (might be one or more) in the argument.
You should display your progress there
Yo can read more about those methods in SwingWorker documentation
How to use SwingWorker in this example
Immediate fix
From documentation of SwingWorker
Type Parameters:
T - the result type returned by this
SwingWorker's doInBackground and get methods
V - the type used for
carrying out intermediate results by this SwingWorker's publish and
process methods
So replace
class Process extends SwingWorker<String, String>
with
class Process extends SwingWorker<DefaultTableModel, Void>
Note:
- It's better if you rename your class from
Process
, there is already a class named[Process][2]
in java. - You have to pass the
String
viaSwingWorker
's constructor. (In this case, have it as a member ofProcess
class and pass it in constructor.
Calling the SwingWorker
In your actionPerformed
method, replace
DefaultTableModel model = createModel(srtPath);
with
Process p = new Process(srtPath);
p.extecute();
DefaultTableModel model = p.get();
// Rest of actionPerformed content.
Doing it in background
Now everything is working, but you have not achieved the desired effect. The ui thread is still waiting for the output.
- Make
execute
method last thing to do inactionPerformed
method. (Ui thread will be released.) Everything else after getting returned
DefaultModel
should be put in overriddendone
method ofSwingWorker
(Process
). (You need to pass required objects from constructor).@Override
protected void done() {
table.setModel(model);
table.setFillsViewportHeight(true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
TableColumn columnA = table.getColumn("#");
columnA.setMinWidth(10);
columnA.setMaxWidth(40);
TableColumn columnB= table.getColumn("Start");
columnB.setMinWidth(80);
columnB.setMaxWidth(90);
TableColumn columnC= table.getColumn("End");
columnC.setMinWidth(80);
columnC.setMaxWidth(90);
}You have to pass
table
object from constrictor ofProcess
.
Therefore in actionPerformed
,
Process p = new Process(srtPath, table);
p.execute();
Hope this helps.
Edit:
Constructor would be like following.
class Worker extends SwingWorker {
private String srtPath;
private JTable table;
public Worker(String srtPath, JTable table) {
this.srtPath = srtPath;
this.table = table;
}
// Other methods.
}
How do I make my SwingWorker example work properly?
Here an updated version of your code which works
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
public class SwingTesting {
public static void main(String[] args) {
EventQueue.invokeLater( new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
JButton button = new JButton();
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new GuiWorker().execute();
}
});
button.setText("Test Me");
frame.getContentPane().add(button);
frame.pack();
frame.setVisible(true);
}
} );
}
}
class GuiWorker extends SwingWorker<Integer, Integer> {
/*
* This should just create a frame that will hold a progress bar until the
* work is done. Once done, it should remove the progress bar from the dialog
* and add a label saying the task complete.
*/
private JFrame frame = new JFrame();
private JDialog dialog = new JDialog(frame, "Swingworker test", true);
private JProgressBar progressBar = new JProgressBar();
public GuiWorker() {
progressBar.setString("Waiting on time");
progressBar.setStringPainted(true);
progressBar.setIndeterminate(true);
dialog.getContentPane().add(progressBar);
dialog.pack();
dialog.setModal( false );
dialog.setVisible(true);
}
@Override
protected Integer doInBackground() throws Exception {
System.out.println( "GuiWorker.doInBackground" );
Thread.sleep(1000);
return 0;
}
@Override
protected void done() {
System.out.println("done");
JLabel label = new JLabel("Task Complete");
dialog.getContentPane().remove(progressBar);
dialog.getContentPane().add(label);
dialog.getContentPane().validate();
}
}
Key point is that setting a model dialog visible blocks until the dialog is disposed. So making it non-modal fixed it + the validate
call on the content pane when you switch components. I also adjusted your main method to run on the EDT, and added some System.out calls. If you remove the setModal( false )
call you will see those statements are not printed until you close the dialog
How can I use SwingWorker safely?
SwingWorker
uses it's own ExecutorService
, which backed by it's own ThreadFactory
which generates daemon Thread
s, meaning that the JVM will be able to exit once all the non-daemon thread's have finished.
Is it safe? That would come down to what it is your SwingWorker
is actually doing, but if you're not holding any resources open or have other processes reliant on the result of the SwingWorker
in order to completely their jobs (and close resources, etc), it should be
Do I use a SwingWorker?
It's best you split up your code into areas of responsibilities. Let's go with three: 1. the worker (ie the upload); 2. the display (ie the JLabel update); 3. integration of the two (the first two are independent of each other, so you'll need something to tie them together).
Abstracting from the actual work, you can use standard interfaces. The first one is just a Runnable
, ie not taking any parameters and not returning anything. The second one is a Consumer<String>
because it takes a String
(to display) but doesn't return anything. The third will be your main control.
Let's start with the control because that's simple:
Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);
This will start the worker in a separate Thread which is what it sounds like you want.
So here's your uploader:
Consumer<String> display = // tbd, see below
Runnable worker = () -> {
String[] progress = {"start", "middle", "finish"};
for (String pr : progress) {
display.accept(pr);
Thread.sleep(1000); // insert your code here
}
}
Note that this worker actually does depend on the consumer; that is somewhat "unclean", but will do.
Now for the display. Having defined it as a Consumer<String>
, it's abstract enough that we can just print the progress on the console.
Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);
You however want to update a JLabel
; so the consumer would look like
Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);
Your actual question
So if you do that, you will notice that your label text doesn't get updated as you expect. That is because the label.setText(s)
gets executed in the thread in which the worker is running; it needs to be inserted in the Swing thread. That's where the SwingWorker
comes in.
The SwingWorker
has a progress
field which is what you can use for your labels; it also has a doInBackground()
which is your actual upload worker thread. So you end up with
class UploadSwingWorker {
public void doInBackground() {
for(int i = 0; i < 3; i++) {
setProgress(i);
Thread.sleep(1000); // again, your upload code
}
}
}
So how does that update your label? The setProgress
raises a PropertyChangeEvent
you can intercept; this done using a PropertyChangeListener
with the signature
void propertyChange(PropertyChangeEvent e)
This is a functional interface, so you can implement this with a lambda, in your case
String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
String value = displays[index];
label.setText(value);
}
and you can add it to the SwingWorker
using
SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker
Simpler
Note that I myself have never used a SwingWorker
this way. The much simpler way to get around the problem that the GUI is not updated from within your worker thread is to call the GUI update using SwingUtilities.invokeLater()
.
Coming back to the initial Consumer<String>
I brought up, you can do
Consumer<String> display = s -> SwingUtilities.invokeLater(
() -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
);
and that should do. This allows you to keep your worker in the more abstract Runnable
and use the usual scheduling mechanisms to run it (ExecutorService.submit()
or CompletableFuture.runAsync()
for example), while still allowing to update the GUI on a similarly simple level.
SwingWorker get a result
Override the done
method - that method will be called when the work is complete. Oracle has a comprehensive tutorial here: Improve Application Performance With SwingWorker in Java SE 6
Also see another SO question: How do I use SwingWorker in Java?
You can call get
to retrieve the results but if the worker isn't done the thread will block until the worker is done. That means if you call get
from the Event Dispatch Thread (EDT) your GUI will be unresponsive if the worker isn't done. You can call isDone
to determine if the worker has completed.
Finally, you can attach a property change listener to be notified of the worker's progress, including when it completes its task. The first link I posted gives an example of that.
SwingWorker in another SwingWorker's done method
SwingWorker
supports PropertyChange
events, that is, you can listener to when the SwingWorker
changes state or updates it's progress...yes, SwingWorker
even supports progress notification, for example
This means you could set up a PropertyChangeListener
to monitor changes to the progress
and state
properties and take appropriate actions...
A worker that simple sets progress updates...
public class LoadMaster extends SwingWorker<Void, Progress> {
@Override
protected Void doInBackground() throws Exception {
System.out.println("Working hard here, nothing to see...");
for (int index = 0; index < 100; index++) {
Thread.sleep(10);
setProgress(index);
}
return null;
}
@Override
protected void done() {
try {
get();
} catch (Exception e) {
}
}
}
A example PropertyChangeListener
...
public class LoadMasterPropertyChanegHandler implements PropertyChangeListener {
private SwingWorkerExample example;
public LoadMasterPropertyChanegHandler(SwingWorkerExample example) {
this.example = example;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
int value = (int) evt.getNewValue();
example.showProgress(value);
} else if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
if (worker.isDone()) {
try {
worker.get();
example.loadCompleted();
} catch (InterruptedException | ExecutionException exp) {
example.loadFailed();
}
}
}
}
}
Now, all this does is sends back information to the SwingWorkerExample
(it's coming) which allows it to determine what it should do...
In this example, the loadCompleted
method updates the UI and then starts the second worker...
protected void loadCompleted() {
//...
LoadStuffWorker stuffWorker = new LoadStuffWorker(this);
stuffWorker.execute();
}
In all truth, I might use interfaces instead, so I'm not overtly exposing the class, but that's a topic for another day...
And the full example...
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class SwingWorkerExample {
private JProgressBar pb;
private JPanel content;
public static void main(String[] args) {
new SwingWorkerExample();
}
public SwingWorkerExample() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
pb = new JProgressBar();
content = new JPanel();
content.setBorder(new EmptyBorder(10, 10, 10, 10));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(content);
frame.setLayout(new GridBagLayout());
frame.add(pb);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
LoadMaster master = new LoadMaster();
master.addPropertyChangeListener(new LoadMasterPropertyChanegHandler(SwingWorkerExample.this));
master.execute();
}
});
}
protected void showProgress(int value) {
pb.setValue(value);
}
protected void loadCompleted() {
content.removeAll();
content.setLayout(new GridLayout(0, 1));
content.add(new JLabel("All your base are belong to us"));
content.revalidate();
LoadStuffWorker stuffWorker = new LoadStuffWorker(this);
stuffWorker.execute();
}
protected void loadFailed() {
content.removeAll();
content.setLayout(new GridLayout(0, 1));
content.add(new JLabel("Fail"));
content.revalidate();
}
protected void setVersion(String value) {
content.add(new JLabel("Version: " + value));
content.revalidate();
}
protected void failed(String fail) {
content.add(new JLabel(fail));
content.revalidate();
}
public class LoadMaster extends SwingWorker<Void, Progress> {
@Override
protected Void doInBackground() throws Exception {
System.out.println("Working hard here, nothing to see...");
for (int index = 0; index < 100; index++) {
Thread.sleep(10);
setProgress(index);
}
return null;
}
@Override
protected void done() {
try {
get();
} catch (Exception e) {
}
}
}
public class LoadStuffWorker extends SwingWorker<String, Void> {
private SwingWorkerExample example;
public LoadStuffWorker(SwingWorkerExample example) {
this.example = example;
}
@Override
protected String doInBackground() throws Exception {
System.out.println("Hanging about in the background");
Thread.sleep(3000);
return "Hello from the dark side";
}
@Override
protected void done() {
try {
String value = get();
example.setVersion(value);
} catch (InterruptedException | ExecutionException ex) {
example.failed("Fail while doing version check");
}
}
}
public class Progress {
}
public class LoadMasterPropertyChanegHandler implements PropertyChangeListener {
private SwingWorkerExample example;
public LoadMasterPropertyChanegHandler(SwingWorkerExample example) {
this.example = example;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
int value = (int) evt.getNewValue();
example.showProgress(value);
} else if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
if (worker.isDone()) {
try {
worker.get();
example.loadCompleted();
} catch (InterruptedException | ExecutionException exp) {
example.loadFailed();
}
}
}
}
}
}
Related Topics
How to Avoid Installing "Unlimited Strength" Jce Policy Files When Deploying an Application
Converting Java Bitmap to Byte Array
Using Static Variables in Android
Setting/Changing the Ctime or "Change Time" Attribute on a File
How to Avoid Java.Net.Bindexception: Address Already in Use
How to Install Java Application to My Linux System
How to Get 0-Padded Binary Representation of an Integer in Java
Java: How to Get a Class Literal from a Generic Type
Android - Save Image into Gallery
Java.Lang.Numberformatexception: Invalid Double: " "
Firebase No Properties to Serialize Found on Class
How to Find My Pid in Java or Jruby on Linux
Java Getlocalhost() Unknownhostexception /Etc/Hosts File Differs Linux API
Why Parallel Execution on Java Compile Take Linear Growth in Time
Command Working in Terminal, But "No Closing Quote" Error When Used Process.Exec