I have a scenario where I need my swing UI to run on two different threads. I have a laptop where I will my application will run. There is a Button on clicking which an presentation should start at the other Screen that is attached to my laptop.
Now I have made a class presentation which is extending SwingWorker and reads the images from a folder and displays it on screen.
class Presenatation extends SwingWorker<Integer, Integer> {
@Override
protected Integer doInBackground() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
start(outputFolder, screenVO);/*Creates a JFrame to be displayed
on new screen and sets a JPanel to it. Reads the file images sets it into
JLabels every 2 seconds and updates it to Japnel*/
}
});
return null;
}
Inside my start method I have the code to read images and show them on the UI
What I feel is this approach is wrong since my SwingWorker shouldn't be calling invokeLater in doInBackground()
From what little knowledge I have, it should be something like this:
@Override
protected Void doInBackground() throws Exception
{
return null;
}
@Override
protected void process(List<Integer> chunks
{
}
I am not able to decide which part should be placed where ?
I have the following things to do :
Please help me !
Indeed, you should not call invokeLater from doInbackground, nor spawning a new thread. Note that SwingWorker is a Runnable, so it can be submitted to an Executor.
But you have to call the publish method every time you have a new image ready to display. Behind the scene the SwingWorker will invoke its process method on the Event Dispatch thread.
So, you have to override process to do the actual widgets update.
Take this answer with a grain of salt because many will disagree and say this is a terrible practice. However, no one, so far as I've seen, can precisely say WHY this is bad. So, until then, I maintain my opinion:
invokeLater()
and invokeAndWait()
within doInBackground()
. There, I said it.
I asked basically the same question last month and I since then I've been experimenting with replacing process()
and publish()
with invokeLater()
. In my experiments, I've not run into any threading or synchronization issues. And the approach is a lot easier than publish()
and process()
.
Why do I say this?
First, by calling invokeLater
, you are throwing whatever code you run to the EDT. So, there's no logical reason why that code should break.
Second, process()
and publish()
were written with very, very specific goals in mind (ie, to provide a way for you to send tabular results so you can update your JTable
or JList
in real-time). I've never, not once, used process()
or publish()
in the way it was obviously written to be used (and I work with a lot of tabular data). In order to get publish()
and process()
to do what I want (90% of the time update an indeterminate JProgressBar
and 9% of the time update a determinate JProgressBar
), I usually end up writing some hackish way of publishing and processing my data. It's confusing, difficult to read, and difficult to manage change requests.
However, invokeLater()
from within doInBackground()
is easy to read and, again, I haven't heard anyone say why it would be unsafe to do. And, as in the case presented in the question I linked above, if you need to pause execution for user feedback, I don't see there being any other option than to invokeAndWait()
.
My honest opinion is that publish()
and process()
weren't very well written. SwingWorker
is amazing once you learn the nuances, but those methods do not cover the needs of most people using SwingWorker
and their need to update the user about progress. At least, not in my experience.
EDIT: Specifically regarding your situation, what I would do is:
Create a constructor for your SwingWorker
and initialize a dialog there, but make it a class member of the SwingWorker
so you can update it. For example:
class Task extends SwingWorker {
IndeterminateLoadingDialog ild; public Task _Task() { JDialog dialog = new JDialog(// parent frame); ild = new IndeterminateLoadingDialog(this); dialog.add(ild); dialog.pack(); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setLocationRelativeTo(dialog.getParent()); dialog.setVisible(true); }
My IndeterminateLoadingDialog
class is a JPanel
that just has a JProgressBar
and a JLabel
. I can change the text through a method that interfaces with the JLabel
.
Anyways, I always initialize whatever GUI components I need for the SwingWorker
in the constructor. Just make sure new Task().execute()
is called from the EDT.
If I need to update my IndeterminateLoadingDialog
, I will just use invokeLater()
to change the text directly.
Further, I close the dialog in the done()
method.
Hope this helps you.
Swing (and all other GUI frameworks) are not multi-threaded (because of a long list of valid reasons).
Therefore a good rule of thumb is to create and operate any GUI components from the EDT thread only . There are edge cases (such as creating a new JFrame from a thread) which might work, but basically it is a bad idea to do anything with the GUI from a different thread than EDT.
You not only should but have to call invokeLater or invokeAndWait from any non-EDT thread. This is the only way to ensure that your thread is getting suspended, and the submitted Runnable is getting executed from the context of the EDT thread.
Basically, you definitely not need two EDT threads. In fact it is a bad idea, you have one keyboard, and one mouse creating one set of UI events, therefore two EDT threads make no use.
In fact, you don't even need a swingworker to switch pictures on the second display. SwingWorkers are great for off-loading long-running non-gui operations from the EDT thread (ie executing a database operation which takes 20 seconds to complete). In your case, loading a new picture is not a rocket science :)
Do the followings:
It is quite simple in real. Consider this example:
package a;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class PresentationStart extends JFrame {
public static void main(final String[] args) {
new PresentationStart();
}
public PresentationStart() {
super("Start here");
final JButton button=new JButton("Start");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
new PresentationView();
}
});
add(button);
pack();
setVisible(true);
}
}
And the viewer:
package a;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class PresentationView extends JFrame {
public PresentationView() {
super("View");
final JLabel picture=new JLabel("Picture comes here");
add(picture);
pack();
setVisible(true);
final List<String> pictures=new ArrayList<String>();
pictures.add("http://storage3d.com/storage/2008.10/49d7c6aeed760176755a7570b55db587.jpg");
pictures.add("http://storage3d.com/storage/2008.10/49d7c6aeed760176755a7570b55db587.jpg");
pictures.add("http://www.alragdkw.com/wp-content/uploads/2016/01/fruit-Banans.jpg");
final Timer timer=new Timer(2000,new ActionListener() {
int index=0;
@Override
public void actionPerformed(final ActionEvent e) {
// Load a new picture
try {
picture.setIcon(new ImageIcon(ImageIO.read(new URL(pictures.get(index)))));
} catch (final Exception ex) {
ex.printStackTrace();
}
index++;
if (index>=pictures.size()) {
index=0;
}
}
});
timer.start();
}
}
Make your presentation as a standalone java program, and start it with Runtime.exec(). It will create a separate window.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.