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 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 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.
java swingworker doesnt work properly it publish but wont process
Basically, from my observations, when this occurs, it's because what's going on in the doBackground
method is preventing what ever is processing the "publish queue" from being able to run.
Try adding a call to Thread.yield
or Thread.sleep
somewhere in the processing loop to allow the "publishing" thread time to process the "publish queue"
import java.io.File;
import java.util.List;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class BackupBackgroundProcess extends SwingWorker<Object, String> {
public BackupBackgroundProcess() {
}
@Override
protected void process(List<String> list) {
System.out.println("PROCESSING " + list.size() + " files");
}
@Override
protected void done() {
}
@Override
protected Object doInBackground() throws Exception {
System.out.println("OK");
File[] root;
root = new File("/home/hilman/Pictures/err").listFiles();
for (File file : root) {
seekFiles(file, this);
}
return null;
}
private void seekFiles(File f, SwingWorker thread) {
if (f.isDirectory()) {
File[] listedFiles = f.listFiles();
for (int i = 0; i < listedFiles.length; i++) {
File file = listedFiles[i];
seekFiles(file, thread);
}
} else {
// System.out.println(" PUBLISHING FILE " + f.getAbsolutePath());
publish(f.getAbsolutePath());
Thread.yield();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new BackupBackgroundProcess().execute();
}
});
}
}
Since you're doing a recursive call, I placed a call to Thread.yield
after calling publish
, but you could just as easily place it at the end of the seek
method.
Remember, the process/publish process buffers results, so when publish
is called, you might get a number of files, for example...
OK
PROCESSING 122 files
PROCESSING 127 files
PROCESSING 184 files
PROCESSING 144 files
PROCESSING 131 files
PROCESSING 147 files
PROCESSING 335 files
...
I tend to use Thread.sleep(1)
, but I'd recommending testing with both a see what results you get...
SwingWorker not responding
if I add Thread.sleep(...), it does work, though, it throws a
InterruptedException
The code that apparently produces the exception (copied from OP's edit):
while (!isCancelled()) {
counter %= arrNames.length;
// System.out.format("Counter : %d%n", counter);
publish(arrNames[counter]);
try {
Thread.sleep(30); // throws
} catch (InterruptedException ie) {
ie.printStackTrace();
}
counter++;
}
The reason, though, is the code that cancels the worker (in the actionListener):
backgroundTask.cancel(true);
which explicitly tells the worker to cancel by .. interrupting the thread. From its api doc:
mayInterruptIfRunning - true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete
As an aside: catching the exception and doing nothing (thereby effectively ignoring the interrupt) is not the best of ideas. Probably not too damaging in this case - due to checking the cancelled status. Typical worker implementations either catch and return, after doing some internal cleanup if needed, or don't handle it at all.
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...
WatchService and SwingWorker: how to do it correctly?
Actually, @Eels's comment didn't stop knocking in the back of my head - and finally registered: it's the way to go, but there is no need for any "artificial" struct, because we already have the perfect candidate - it's the PropertyChangeEvent itself :-)
Taking the overall process description from my question, the first three bullets remain the same
- same: extend SwingWorker
- same: do the registration stuff in the constructor
- same: put the endless loop waiting for a key in doInBackground
- changed: create the appropriate PropertyChangeEvent from each WatchEvent when retrieved via key.pollEvents and publish the PropertyChangeEvent
- changed: fire the previously created event in process(chunks)
Revised FileWorker
:
@SuppressWarnings("unchecked")
public class FileWorker extends SwingWorker<Void, PropertyChangeEvent> {
public static final String FILE_DELETED = StandardWatchEventKinds.ENTRY_DELETE.name();
public static final String FILE_CREATED = StandardWatchEventKinds.ENTRY_CREATE.name();
public static final String FILE_MODIFIED = StandardWatchEventKinds.ENTRY_MODIFY.name();
// final version will keep a map of keys/directories (just as in the tutorial example)
private Path directory;
private WatchService watcher;
public FileWorker(File file) throws IOException {
directory = file.toPath();
watcher = FileSystems.getDefault().newWatchService();
directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
@Override
protected Void doInBackground() throws Exception {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return null;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
continue;
}
publish(createChangeEvent((WatchEvent<Path>) event, key));
}
// reset key return if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
break;
}
}
return null;
}
/**
* Creates and returns the change notification. This method is called from the
* worker thread while looping through the events as received from the Watchkey.
*
* @param event
* @param key
*/
protected PropertyChangeEvent createChangeEvent(WatchEvent<Path> event, WatchKey key) {
Path name = event.context();
// real world will lookup the directory from the key/directory map
Path child = directory.resolve(name);
PropertyChangeEvent e = new PropertyChangeEvent(this, event.kind().name(), null, child.toFile());
return e;
}
@Override
protected void process(List<PropertyChangeEvent> chunks) {
super.process(chunks);
for (PropertyChangeEvent event : chunks) {
getPropertyChangeSupport().firePropertyChange(event);
}
}
}
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.
Background SwingWorker thread not executing a particular piece of code
Immediately off the bat I see that you're making Swing calls from within the SwingWorker's doInBackground, something which completely defeats the purpose of using a SwingWorker or background thread. Also, the call you're making, one that displays a modal dialog:
waitingDialog.setVisible(true); // **** here ****
MyCustomClass.run(); // **** not sure what this does or if this makes Swing calls or not.
will pause all code flow below it until the dialog is no longer visible, as that is how modal dialogs function. This is what is blocking your SwingWorker. You must try to separate your code so that Swing calls are only made on the Swing event thread, and use the worker for only the background work.
Instead
- Create your SwingWorker
- Execute your SwingWorker
- Then call
setVisible(true)
on your modal dialog
In pseudocode:
create modal dialog
create SwingWorker
Attach any PropertyChangeListeners to the SwingWorker (if needed)
Execute the SwingWorker
Display the modal dialog
So, the first quick change I would make in your code above would be to remove waitingDialog.setVisible(true);
from your SwingWorker doInBackground()
method, and then call the waitingDialog.setVisible(true);
method AFTER executing the SwingWorker, i.e., after backgroundThread.execute(); in particular. I know it seems counter-intuitive to call dispose on it seemingly before making it visible, but trust me, it will work better this way, since the dispose call will be blocked by the time it takes to run the background thread.
Related Topics
Replace the Last Part of a String
Create a Autocompleting Textbox in Java with a Dropdown List
Removing Invalid Xml Characters from a String in Java
Springboot 2.6.0/Spring Fox 3 - Failed to Start Bean 'Documentationpluginsbootstrapper'
How to Get My Maven Integration Tests to Run
How to Daemonize a Java Program
Java; String Replace (Using Regular Expressions)
Named Placeholders in String Formatting
Make Arraylist.Toarray() Return More Specific Types
The Server Time Zone Value 'Aest' Is Unrecognized or Represents More Than One Time Zone