Good day,
I've read a few other stack overflow postings and other tutorials, but I can't get my GUI to update correctly after a button starts a long process. I've attached the full code of the problem that I am having. Notice if you run the code, the JList gets updated all at once at the end instead of every iteration of the for loop.
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class theframe extends JFrame implements ActionListener
{
private JList list;
private DefaultListModel listmodel;
private JButton start;
public theframe()
{
listmodel = new DefaultListModel();
list = new JList(listmodel);
start = new JButton("Start");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500,500);
setVisible(true);
list.setPreferredSize(new Dimension(200,200));
start.addActionListener(this);
JPanel p = new JPanel();
p.add(start);
p.add(list);
this.add(p);
}
public static void main(String[] args)
{
theframe frame = new theframe();
}
#Override
public void actionPerformed(ActionEvent arg0)
{
if(arg0.getSource() == start)
{
for(int i=0;i<10;i++)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
// the JList should update one by one
listmodel.addElement("Start pushed ");
}
});
try
{
//This thread sleep simulates a long job
Thread.sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
Any help would be greatly appreciated.
Your problem here is the fact that you are calling the invokeLater method already from the EDT (Event Dispatching Thread).
The method actionPerformed is called from the EDT so what happens is that the sleep call just stops the EDT itself: you can imagine that this is not how it should work, no EDT running means no GUI updates.
Since it's a time consuming task you should implement it in a Thread/Runnable so that you can execute it in parallel and then call the invokeLater from this other thread.
Something like:
class LongProcess extends Thread {
public void run() {
for (int i = 0; i < 10; ++i) {
SwingUtilities.invokeLater(...);
Thread.sleep(300);
}
}
}
void actionPerformed(ActionEvent e) {
LongProcess process = new LongProcess();
process.start();
}
The actionPerformed method will be called on the Event Dispatch Thread. Calling Thread.sleep on the EDT stops it from updating your GUI. Since your GUI cannot update, your JList will not repaint itself when items are added to it until after your loop exits.
You should probably be using a SwingWorker. (SwingWorker tutorial.)
Related
This question already has answers here:
Updating GUI from a runnable
(3 answers)
Closed 5 years ago.
I have a GUI that I have developed that has a couple of buttons and a JTextArea for output. I have some code below that executes a function and updates the TextArea, however before the function is executed I would like the TextArea to be updated with the string "Processing...", just to give the user some kind of idea that the application is working as the function takes a while to execute.
At the moment this code doesn't update the GUI element and I understand why. The GUI doesn't get a chance to repaint in between the two commands that change the TextArea, so the "processing..." string is never displayed. How do I change the code so that the GUI element updates before the Main.featureAnalysisTop() function executes?
if(e.getActionCommand().equals("Extract Features"))
{
featuresTextArea.setText("Processing...");
int nFeatures = nFeatureSlider.getValue();
Main.featureAnalysisTop(nFeatures);
featuresTextArea.setText("");
ArrayList<String> featureList = Main.getFeatureList();
for(String str : featureList)
{
featuresTextArea.append(str + "\n");
}
The GUI is executed in my main method using the following code.
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
gui = new GUI2();
gui.setLocationRelativeTo(null);
gui.frmNeuralNetwork.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
Updating GUI on separate thread while other method is executing
That's a job for the Swing Woker class, which allows you to create a separate thread that runs in the background and lets you update your GUI accordingly.
For example, I made a simple example that accomplishes what you're trying to do.
First we create the GUI and add an ActionListener to our JButton where it starts our Swing Worker after updating the JTextArea's text to processing... as you were trying to do it and after 5 seconds (which simulates your long running task) it updates to I'm done!.
You can try it and change the code accordingly.
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class TextAreaUpdater {
private JFrame frame;
private JTextArea area;
private JButton button;
private SwingWorker<String, String> worker;
public static void main(String[] args) {
SwingUtilities.invokeLater(new TextAreaUpdater()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
area = new JTextArea(10, 30);
button = new JButton("Update!");
button.addActionListener(listener);
worker = new SwingWorker<String, String>() {
#Override
protected String doInBackground() throws Exception {
try {
Thread.sleep(5000); //Simulates long running task
} catch (InterruptedException e) {
e.printStackTrace();
}
return "I'm done!"; //Returns the text to be set on the JTextArea
}
#Override
protected void done() {
super.done();
try {
area.setText(get()); //Set the textArea the text given from the long running task
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};
frame.add(area, BorderLayout.CENTER);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private ActionListener listener = (e -> {
area.setText("Processing...");
worker.execute(); //Initializes long running task
});
}
References:
Swing Worker Example
How do I use SwingWorker in Java?
Here's another example that uses SwingWorker: update jlabel text after opening jdialog
Hope it helps
I am trying to follow the Java best practices by not doing long tasks on the main thread (EDT). So I am planning to use a swingWorker with Modal Dialog. This way the modal dialog blocks the user for doing anything until that task is done and I can update status on the dialog while the process is taking place.
Now the problem is that with the modal dialog, it not only blocks the user but also nothing after setVisible gets called
So if I do
dialog.setVisible(true);
new SwingWorkerTask().execute(); //This does not get called
and if I do
new SwingWorkerTask().execute();
dialog.setVisible(true); // Well what the point of setting visible after the fact.
So How do I block user action and show a dialog while task is taking place?
Thank you
It is only a chicken/egg if you make it such. You can construct all Swing objects on EDT and then let your SwingWorker (or any other thread) govern all updates by instructing EDT to execute them via SwingUtilities.invokeLater(Runnable).
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class RudeProgressBar extends JFrame {
private JButton button;
public RudeProgressBar() {
setTitle("Rude Progress Bar");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
button = new JButton("Do teh work");
add(button, BorderLayout.SOUTH);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JDialog dialog = new JDialog(RudeProgressBar.this, true);
dialog.setTitle("Doing teh work");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
final JProgressBar progressBar = new JProgressBar(0, 100);
dialog.setLayout(new BorderLayout());
dialog.add(progressBar);
dialog.setSize(100, 100);
dialog.setLocationRelativeTo(RudeProgressBar.this);
MyTask task = new MyTask(dialog);
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
}
});
setSize(200, 200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new RudeProgressBar().setVisible(true);
}
});
}
private class MyTask extends SwingWorker<Void, Void> {
private final JDialog dialog;
public MyTask(JDialog dialog) {
this.dialog = dialog;
}
#Override
protected Void doInBackground() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
dialog.setVisible(true);
}
});
int progress = 0;
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
setProgress(progress += 20);
}
return null;
}
#Override
protected void done() {
dialog.setVisible(false);
dialog.dispose();
}
}
}
If you are worried that the invokeLater implementation (inside SwingWorker.doInBackground) might get executed after SwingWorker.done, simply put the code in done into another invokeLater. By doing this, you queue your Runnable implementations for EDT to execute them in certain order. The queuing will happen even if this method is called from EDT itself.
Note that if you take a look at SwingWorker implementation, you'll see that it relies on javax.swing.Timer to execute done() and the Timer itself calls invokeLater, so calling it inside done again amounts to doing nothing. Nothing will be wrong if you do it, however.
You can try SwingUtilities.invokeLater and SwingUtilities.invokeAndWait instead of swingWorker.
Also, this topic may be useful.
What is the correct way of disposing a frame which is created inside a Runnable object?
The code below returns a null pointer exception when the endDialog is called before the LoadingRunnable has completed its constructor.
How can the endDialog be executed after the constructor has finished?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LoadingRunnable implements Runnable
{
private JFrame jFrame;
#Override
public void run()
{
jFrame = new JFrame("Window");
JPanel jPanel = new JPanel();
JLabel label = new JLabel("Loading...");
jPanel.add(label);
jFrame.setContentPane(jPanel);
jFrame.pack();
jFrame.setVisible(true);
}
public void endDialog()
{
jFrame.setVisible(false);
jFrame.dispose();
}
public static void main(String args[])
{
LoadingRunnable l = new LoadingRunnable();
SwingUtilities.invokeLater(l);
//work done here
l.endDialog();
}
};
You have a concurrency problem here because SwingUtilities.invokeLater() schedules your runnable class execution in the Event Dispatch Thread asynchronously while your main thread's flow still running, causing a NPE.
The correct way to dispose a frame is through events, just as Swing is designed to be used. For instance by clicking the "X" (close) button or by dispatching a WindowEvent:
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
You may want to take a look to this question too: Optional way to close a dialog window
In addition
If you just want to show something during your application start up, then you can use SplashScreen API instead of JFrame. See How to Create a Splash Screen for further details.
Based on your previous question and this new one, I'd suggest you read the whole Concurrency in Swing tutorial to understand about common concurrency problems in Swing and how to deal with them.
Ok found how:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Loading
{
private JFrame jFrame;
public void startDialog()
{
jFrame = new JFrame("Window");
JPanel jPanel = new JPanel();
JLabel label = new JLabel("Loading...");
jPanel.add(label);
jFrame.setContentPane(jPanel);
jFrame.pack();
jFrame.setVisible(true);
}
public void endDialog()
{
jFrame.setVisible(false);
jFrame.dispose();
}
public static void main(String args[])
{
final Loading l = new Loading();
for (int i = 0; i < 200; i++)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
l.startDialog();
}
});
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
l.endDialog();
}
});
}
}
};
Is there a way how to use a dialog in Swing which prohibits any gui activity under it but at the same time DOES NOT stop execution on the thread where it was set to visible?
Yes it can be done .
dlg.setModal(false);
or
dlg.setModalityType(Dialog.ModalityType.MODELESS);
where dlg is instance of your JDialog .
The basic idea of a JDialog IS to block the underlying thread until the user reacts to it. If you need to run something on the UI thread which should not be interrupted, consider using an additional worker thread for it. This way, the UI will be blocked by the JDialog, but the underlying process won't.
Yes, there is a little trick to make it work. We simply deactivate modality and manually disable the JFrame we want to make unclickable.
private final static JDialog dialog; static {
JOptionPane pane = new JOptionPane();
pane.setOptions(new Object[]{}); // Removes all buttons
dialog = pane.createDialog(frame, ""); // Create dialog with pane
dialog.setModal(false); // IMPORTANT! Now the thread isn't blocked
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
}
Now you can use it like this:
dialog.setVisible(true);
frame.setEnabled(false);
// Logic
dialog.setVisible(false);
frame.setEnabled(true);
Technically, no. Like MadProgrammer wrote in a comment, you are never expected to access any Swing component off-EDT, JDialogs included, therefore the situation you hinted at in the question can never happen (there can never be any thread other than EDT that sets a dialog visible).
You could make it seem like it is, though. That's what SwingUtilities.invokeLater(Runnable) is for (doc).
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class BlockingDialogDemo extends JFrame {
private Timer timer;
private JDialog blocker;
public BlockingDialogDemo() {
setTitle("Blocking Dialog");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 200);
setLocationRelativeTo(null);
blocker = new JDialog(this, true);
blocker.setLayout(new FlowLayout());
blocker.setUndecorated(true);
blocker.getRootPane().setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black));
blocker.add(new JLabel("I'm blocking EDT!"));
JProgressBar progress = new JProgressBar();
progress.setIndeterminate(true);
blocker.add(progress);
blocker.pack();
timer = new Timer(3000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
doSomeWork();
}
});
timer.setRepeats(false);
timer.start();
}
private void doSomeWork() {
// this executes on-EDT
Runnable runnable = new Runnable() {
public void run() {
// this executes off-EDT - never ever access Swing components here
showBlocker();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("Ummm.. I was sleeping here!");
} finally {
hideBlocker();
}
}
};
new Thread(runnable).start();
}
private void showBlocker() {
// this executes off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this executes on-EDT
blocker.setLocationRelativeTo(BlockingDialogDemo.this);
blocker.setVisible(true);
}
});
}
private void hideBlocker() {
// this executes off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this executes on-EDT
blocker.setVisible(false);
timer.restart();
}
});
}
public static void main(String[] args) {
// this is called off-EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// this is called on-EDT
new BlockingDialogDemo().setVisible(true);
}
});
}
}
This works for me... sometimes:
public class NonBlockingModalDialogDemo extends JFrame{
JButton btnDoIt;
public NonBlockingModalDialogDemo() {
setTitle("NonBlockingModalDialog Demo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300,300);
setLayout(new FlowLayout());
btnDoIt = new JButton("Non-Blocking Notify");
btnDoIt.addActionListener( new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
JDialog asyncDialog = createNonBlockingModalDialog("Please wait while we do some work", "Please wait");
doWork(50);
//Once your done, just dispose the dialog to allow access to GUI
asyncDialog.dispose();
}
});
this.add(btnDoIt);
}
private JDialog createNonBlockingModalDialog(String message, String title)
{
final JDialog dialog = new JDialog();
dialog.setLayout(new FlowLayout());
dialog.add(new JLabel(message));
dialog.setTitle(title);
dialog.setModal(true);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setAlwaysOnTop(true);
dialog.pack();
Runnable dialogDisplayThread = new Runnable() {
public void run() {
dialog.setVisible(true);
}};
new Thread(dialogDisplayThread).start();
//Need to wait until dialog is fully visible and then paint it
//or else it doesn't show up right
while(!dialog.isVisible()){/*Busy wait*/}
dialog.paint(dialog.getGraphics());
return dialog;
}
private void doWork(int amount) {
for(int i = 0; i < amount; i++)
{
System.out.println("Doing work step number " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("done");
}
public static void main(String[] args) {
new NonBlockingModalDialogDemo().setVisible(true);
}
}
I don't really like that it has a busy wait in it to check if the Dialog is visible yet, but so far I haven't found a way around it. At any rate, the busy wait should not take very long at all, so it really shouldn't matter.
Edit:
I did something very similar to this and for some reason, on some machines, sometimes, it just blocks forever without even showing the dialog.
I haven't figured out the root cause, but this leads me to conclude that all the people who say "never modify the GUI outside of the Event Dispatch Thread" may be on to something.
Perhaps rather than trying to continue the work you need to do on the EDT, maybe you should try something like this:
https://stackoverflow.com/a/4413563/2423283
Which uses SwingWorker to spawn a new thread and then allows you to update the GUI components when you are done.
I'm trying to update the main gui in a java swing application, so there is a runnable thread that keeps the main gui visible, but the problem is it is called in main, and main is a static function. I would like to say Element.SetTtext. But all calls that I want to update are not static. How can I update the lables,..etc in the Main GUI then?
public static void main(String args[])
{
java.awt.EventQueue.invokeLater(new Runnable() {
public void run()
{
new AGC().setVisible(true);
// code to update labels here
}
});
}
What I understood from your question is that you think static means non-changeable. This is not true with Java. In Java objects and components that never change are characterized as final.
Keep your main simple and small and make your loops and changes in doThings();
Here is a Timer in order to update the text of the JLabel:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Foo extends JFrame {
public Foo() {
jLabel1 = new JLabel("label 1");
jPanel1 = new JPanel();
jPanel1.add(jLabel1);
add(jPanel1);
pack();
// code to update whatever you like here
doThings();
}
private void doThings() {
// code to update whatever you like here
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
jLabel1.setText("foo " + (j++));
}
};
Timer timer = new Timer(500, actionListener);
timer.start();
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Foo().setVisible(true);
}
});
}
private JLabel jLabel1;
private JPanel jPanel1;
private int j = 0;
}
little more clarity is required , when do u want to update the labels ? is it based on an event ?
You can always keep a global variable of the component you want to update and access it from the event handlers.
Can you please update your question with the code , so that it gives a better clarity ?