I'm developing a Swing app, and I need to run an infinite loop in the background (which runs until: 1) the cancel button of my JDialog is selected or 2) the input data it is searching for is found) while a modal dialog shows an indeterminate progress bar.
Something I've noticed is that if the JDialog is modal, then the SwingWorker will not execute its tasks until the JDialog is closed (and releases its deathgrip on the EDT, I guess...?). If the JDialog is not modal, then SwingWorker's tasks will execute happily in the background.
I've been doing some research, but I'm no thread/EDT expert and am having a hard time figuring the reason/solution.
Any input on this situation/threads/EDT/SwingWorker, or a suggested solution, would be greatly appreciated.
(Question pulled directly from: http://www.coderanch.com/t/346275/GUI/java/SwingWorker-Modal-JDialogs)
I tried the solution regarding the setVisible call of the JDialog like this user found to be the solution, but I still can't execute both threads simultaneously. Any help would be appreciated.
Relevant:
public Dialog(JFrame parentFrame, String equipmentName) {
super(parentFrame, "Progress");
this.hasRequestedCancel = false;
this.equipmentName = equipmentName;
add(createMainPanel());
setIconImage(Toolkit.getDefaultToolkit().getImage(SomeClass.class.getResource(ICON_PATH)));
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
setModalityType(ModalityType.DOCUMENT_MODAL);
pack();
setSize(550, 100);
setResizable(false);
setLocationRelativeTo(parentFrame);
setVisible(true);
}
And
SwingWorker<File, Void> worker = createSwingWorker(params, ...);
worker.execute();
And
private SwingWorker<File, Void> createSwingWorker(final File someFile, final SomeClass asdf, final String param3) throws IOException {
SwingWorker<File, Void> swingWorker = new SwingWorker<File, Void>() {
#Override
protected File doInBackground() throws IOException {
Dialog progressBar = new Dialog(SomeClass.this, SomeClass.this.equipManufacturerDevice);
try {
while(!someFile.exists() && !progressBar.hasRequestedCancel()) {
Thread.sleep(SomeClass.SLEEP_DURATION);
System.out.println("yo");
}
} catch (InterruptedException e) {
}
...
}
#Override
protected void done() {
...
}
};
return swingWorker;
}
The problem is that you are calling setVisible(true) inside the Dialog’s constructor which is a discouraged practice anyway (you just found one reason, why).
Separate the creation and opening of the dialog and you don’t have that problem anymore. The following sample code demonstrates how this can be achieved:
final Dialog d=new Dialog((Window)null);
d.setSize(300, 300);
d.setModal(true);
new SwingWorker<Object,Object>() {
#Override
protected Object doInBackground() throws Exception {
System.out.println("long running stuff");
TimeUnit.SECONDS.sleep(10);
System.out.println("end of long running stuff");
return null;
}
#Override
protected void done() {
d.dispose();
}
}.execute();
System.out.println("before setVisible(true)");
d.setVisible(true);// will block
System.out.println("after setVisible(true)");
What if you moved the data input logic from the main frame and kept it running on a separate, dedicated, background thread whose sole job is to listen for connections and handle them. This would leave your parent JFrame to handle UI interactions thereby giving you the freedom to freeze it when one of your JDialog has focus.
Related
I originally was attempting to update a JFrame and JPanel several times while in a Java Action Listener, but both would only update when the Action Listener completed all its tasks. Here is the link to my original question (Refreshing a JFrame while in an Action Listener).
I was told in the feedback to that question that Swing Worker should solve my problems. However, when I implemented Swing Worker (as seen below), nothing changed. The JFrame and JPanel still updated only when the Action Listener completed all tasks. My question is, am I missing something below? If not, how can I implement this in an Action Listener to properly update the Frame and Panel timely?
#Override
protected Integer doInBackground() throws Exception{
//Downloads and unzips the first video.
if(cameraBoolean==true)
panel.add(this.downloadRecording(camera, recording));
else
panel.add(new JLabel("Could not contact camera "+camera.getName()));
panel.repaint();
jframe.repaint();
return 1;
}
private JLabel downloadRecording(Camera camera, Recording recording){
//does a bunch of calculations and returns a jLabel, and works correctly
}
protected void done(){
try{
Date currentTime = new Timestamp(Calendar.getInstance().getTime().getTime());
JOptionPane.showMessageDialog(jframe, "Camera "+camera.getName()+" finished downloading at "+currentTime.getTime());
}catch (Exception e){
e.printStackTrace();
}
}
You have a misundertanding about how SwingWorker works. This class is intended to provide a way to update the GUI while heavy tasks are being performed. All of this is because Swing components updates take place in the Event Dispatch Thread (a.k.a. EDT) which is a particular thread.
For instance, if you click a button and perform a time consuming task all in the EDT, then this thread will block untill this task finishes. Consequently, you'll see your GUI is frozen.
Keeping this in mind, doInBackground() method runs in another different thread that's not the EDT which is ok. So don't call any Swing method in there:
protected Integer doInBackground() throws Exception{
//Downloads and unzips the first video.
if(cameraBoolean==true) // just use if(cameraBoolean), since this is a boolean
panel.add(this.downloadRecording(camera, recording)); // NO!
else
panel.add(new JLabel("Could not contact camera "+camera.getName())); //NO!
panel.repaint(); //NO, never!
jframe.repaint();//NO, never!
return 1;
}
Add a JLabel to this panel before executing your SwingWorker and update its text using publish() and process() methods instead:
JPanel panel = new JPanel();
final JLabel progressLabel = new JLabel("Some text before executing SwingWorker");
panel.add(progressLabel);
SwingWorker<Integer, String> worker = new SwingWorker<Integer, String>() {
#Override
protected Integer doInBackground() throws Exception {
if(cameraBoolean){
pubish("Starting long process...");
//Some processing here
publish("Intermediate result to be published #1");
//Some other processing stuff
publish("Intermediate result to be published #2");
//And so on...
return 0;
} else {
publish("Could not contact camera "+camera.getName());
return -1;
}
}
#Override
protected void process(List<String> chunks) {
for(String string : chunks){
progressLabel.setText(string);
}
}
#Override
protected void done() {
progressLabel.setText("Finished!!!");
}
};
worker.execute();
Both process() and done() methods take place in the EDT so it's safe make GUI updates there. Take a look to this excelent example: Swing Worker Example for more details.
Maybe because you repaint your panel/frame just when the synchronous call this.downloadRecording(camera, recording) is finished?
Try to only put this call into the doInBackground() method, because (so I guess) that's the one that takes a long time and for all this time the JFrame gets not refreshed.
You can't update UI in next way:
panel.repaint();
jframe.repaint();
In your doInBackground method you must to call publish(V... chunks) method, that Sends data chunks to the process(java.util.List<V>) method.(according docs) and than in method process(List<V> chunks) you can update your UI(according docs process method - Receives data chunks from the publish method asynchronously on the Event Dispatch Thread.). SwingWorker docs.
So, override process method for updating, and call publish method.
Also you can use Executors for background processes. In this case your UI will be working in EDT and your background process in another thread. Example:
Executors.newSingleThreadExecutor().execute(new Runnable() {
#Override
public void run() {
// run background process
}
});
EDIT: good example of SwingWorker
I have an action added to a JButton created, following is my code
private void myButtonActionPerformed(java.awt.event.ActionEvent evt) {
txtResult.setText("");
myButton.setText("Working ...");
myButton.setEnabled(false);
myButton.repaint();
System.out.println("Doing Action ...");
SwingUtilities.invokeLater(new Runnable() {
public void run() { // some code inside that is memory intensive
}
});
segmentButton.setText("Original Text");
segmentButton.setEnabled(true);
}
While I am able to see the system out, my component is not getting updated at all, more over I am unable to update any other component on the JFrame as if the whole thread is blocked
The answer was that the main thread gets blocked due to the singular nature of swing main thread.
"The Swing single-thread rule: Swing components and models should be
created, modified, and queried only from the event-dispatching
thread."
—Java Concurrency in Practice.
I have updated my code to accommodate the blocker code in a SwingWorker as described below
private void myButtonActionPerformed(java.awt.event.ActionEvent evt) {
txtResult.setText("");
myButton.setText("Working ...");
myButton.setEnabled(false);
myButton.repaint();
System.out.println("Doing Action ...");
SwingWorker worker = new SwingWorker() {
#Override
protected Object doInBackground() throws Exception {
//Memory intensive code
}
#Override
protected void done() {
segmentButton.setText("Original Text");
segmentButton.setEnabled(true);
super.done(); //To change body of generated methods, choose Tools | Templates.
}
};
worker.execute();
}
Thanks #copeg for putting me on the right direction.
I have a problem while creating a JProgressBar which is set to indeterminate.
The following code is my implementation of the JProgressBar and is called/constructed from another class:
public class Progress implements Runnable
{
private JFrame frameProgress;
private JProgressBar progressBar;
public Progress(String title, String message)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
GlobalVariables.LOGGING_logger.error("Error instatiating progress bar.",
e);
}
UIManager.put("ProgressBar.selectionForeground", Color.black);
UIManager.put("ProgressBar.selectionBackground", Color.black);
this.frameProgress = new JFrame(title);
this.frameProgress.setIconImage(GlobalVariables.GUI_icon.getImage());
this.frameProgress.setSize(300, 60);
this.frameProgress.setLocation(16, 16);
this.progressBar = new JProgressBar();
this.progressBar.setStringPainted(true);
this.progressBar.setString(message);
this.progressBar.setIndeterminate(true);
this.frameProgress.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frameProgress.add(this.progressBar);
this.frameProgress.setResizable(false);
this.frameProgress.setVisible(true);
}
public void start()
{
new Thread(this).start();
}
public void close()
{
this.frameProgress.dispose();
this.frameProgress = null;
this.progressBar = null;
}
#Override
public void run()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
}
}
The caller of this JProgressBar is the following code snippet:
Progress p = new Progress("bla", "blub");
p.start();
boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this);
p.close();
And now i want, while the xmlWriter.writeCommonSettingsFromGUI(this); is doing something, that the JProgressBar is shown to the user and is working while the algorithm is running.
How can I achieve this? I don't know so much about threading and searched in many other forums, but I don't found any answer for my question.
Please help me and thank you in advance ;)
EDIT:
The Progress JFrame opens up with no content for that time, the algorithm is running.
You are probably facing concurrency issues with Swing. Assuming that the following code runs on the EDT (Event Dispatching Thread):
Progress p = new Progress("bla", "blub");
eventually, this will open a JFrame with a progress bar in it.
I would consider using a JDialog instead of a JFrame
I would not force the size of the JFrame, but rather call pack()
Then, still running on the EDT (and thus blocking all UI-events such as repaint, mouse clicks, etc...), you call p.start() which starts a new Thread() which will invoke run() which itself calls
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
This basically won't do anything except push an additional event on the EventQueue and it will run after all currently pending events. This event will run... "nothing" since your Runnable is just empty. The new Thread dies almost immediately. So all this code is useless.
Still pursuing on the EDT, you call boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this); (btw, "successful" ends with only one 'l'). This will continue on blocking the EDT, preventing repaints from occurring and preventing the JProgressBar from painting itself. Eventually you will dispose the JFrame but since all this code is running on the EDT, the user will not see much of the progress bar and the UI will look frozen.
Consider reading the Swing tag wiki (especially the very last part with 3 important links).
Using a SwingWorker should help you out in this.
I am writing a piece of code using Java Swing. Basically what it does is that it processes some lengthy task. While the task is running, I want to have a waiting pop-up window with a GIF image in it.
My question is that
final InfoDialog infoDialog = new InfoDialog("Parsing file: " + fileToBeUploaded.getName());
final File finalFileToBeUploaded = fileToBeUploaded;
class FileParsingWorker extends SwingWorker<Void, String> {
#Override
protected Void doInBackground() throws Exception {
String text = fileParsers.parseFile(finalFileToBeUploaded);
publish(text);
return null;
}
#Override
protected void process(List<String> chunks) {
infoDialog.setVisible(false);
}
}
infoDialog.setVisible(true);
FileParsingWorker fileParsingWorker = new FileParsingWorker();
fileParsingWorker.execute();
The InfoDialog is the small UI pop-up window with a GIF animation in it. Basically, I put the lengthy task in the worker but the UI's setVisibles in two places. I am thinking if there is any ways I can run the InfoDialog UI in a thread so that I can reuse that bit of code?
The problem I have is that I want to try to run the InfoDialog indefinitely until I deliberately stop it. If I put setVisible(true) in a thread, that thread immediately terminates and my UI won't be updated.
Can someone show me how to do this?
Please have a read on Concurrency in Swing specifically The Event Dispatch Thread. This is the thread on which all Swing components should be created and manipulated. i.e:
SwingUtilities.invokeLater(new Runnable () {
#Override
public void run() {
final InfoDialog infoDialog = new InfoDialog("Parsing file: " + fileToBeUploaded.getName());
final File finalFileToBeUploaded = fileToBeUploaded;
...
infoDialog.setVisible(true);
FileParsingWorker fileParsingWorker = new FileParsingWorker();
fileParsingWorker.execute();
}
});
Also I think another problem is you set the dialog back to invisible in overriden process(List<String> chunks) of the Swing worker, thus as the first chunk is read the dialog will be closed. I think Swing Workers done() method might be more what you want, and its executed on EDT:
class FileParsingWorker extends SwingWorker<Void, String> {
#Override
protected Void doInBackground() throws Exception {
String text = fileParsers.parseFile(finalFileToBeUploaded);
publish(text);
return null;
}
#Override
protected void process(List<String> chunks) {
//each chunk will get processed here
}
#Override
protected void done() {//when Swing worker is finished this method is called
infoDialog.setVisible(false);
}
}
I have a save button in a JFrame ;on clicking save the 'save' text sets to 'saving....'; I need to set that text as 'saved' after a delay of 10 seconds.How is it possible in java?
Please help...
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
This is what i did...but this wont shows as 'saving' during that delayed time.
If you want to provide the user with visual feedback that something is going on (and maybe give some hint about the progress) then go for JProgressBar and SwingWorker (more details).
If on the other hand you want to have a situation, when user clicks the button and the task is supposed to run in the background (while the user does other things), then I would use the following approach:
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
button.setEnabled(false); // change text if you want
new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
// Do the calculations
// Wait if you want
Thread.sleep(1000);
// Dont touch the UI
return null;
}
#Override
protected void done() {
try {
get();
} catch (Exception ignore) {
} finally {
button.setEnabled(true); // restore the text if needed
}
}
}.execute();
}
});
Finally, the initial solution that was using the Swing specific timer:
final JButton button = new JButton("Save");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Take somehow care of multiple clicks
button.setText("Saving...");
final Timer t = new Timer(10000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
button.setText("Saved");
}
});
t.setRepeats(false);
t.start();
}
});
This question & first 3 answers are heading down the wrong track.
Use a JProgressBar to show something is happening. Set it to indeterminate if the length of the task is not known, but presumably you know how much needs to be saved and how much is currently saved.
Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Use a SwingWorker for long running tasks. See Concurrency in Swing for more details.
The best is to use a timer and its method execute with a delay : http://developer.apple.com/library/mac/documentation/java/reference/javase6_api/api/java/util/Timer.html#schedule(java.util.TimerTask, long). Use a timertask to wrap your runnable and that's it.