How to Use Swingworker in Java

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 via SwingWorker's constructor. (In this case, have it as a member of Process 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 in actionPerformed method. (Ui thread will be released.)
  • Everything else after getting returned DefaultModel should be put in overridden done method of SwingWorker (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 of Process.

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

Worker

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



Leave a reply



Submit