When I try to use any kind of delay on my code the program gets delayed but the JLabel that was put before the delay gets updated after the program ends.
I want the program to:
update the JLabel on the GUI
wait for 5 seconds
update the JLabel again with diferent text
wait another 5 seconds
I have tried with timers, invokelater, invokeandwait, thread.sleep and others.
The problems is that the GUI does get delayed at the right spot but the GUI does not update the JLabel ath place where the code is located. The JLabel gets updated after the program ends.
I want the user to be able to read the text for 5 seconds then read another text for another 5 seconds in order. I do not want the program to run the gui pause at a cetain spot then at end just update the JLabel. I want the gui to get updated before the delay. I do not want the same thing that when I used a timer to happen where I type in a setText for the JLabel before the Timer is typed and then when I run the program the timer works but the JLabel gets updated after the delay(It is not what I want).
The way to achieve that is either using a Swing Worker, either a a Swing Timer. A Swing worker runs a task in background and at the same time it is capable of publishing GUI changes in the Event dispatch thread (the thread where the GUI runs). A SwingTimer can be considered as a simplified version of a Swing Worker, that only runs a task after some time in the Event Dispatch Thread. (Consider a worker that sleeps the thread in background, and after the sleep, it runs the task in the Gui thread).
There are a lot of examples online, like this one and this one.
If you do not want to do something in background, a Timer solution sounds "simpler". Take a look at How can I pause/sleep/wait in a java swing app?
An example where it is closer to what you need (with a worker):
public class WorkerExample extends JFrame {
private static final long serialVersionUID = 6230291564719983347L;
private JLabel label;
public WorkerExample() {
super("");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
publish("Started....");
Thread.sleep(1500);
for (int i = 0; i < 5; i++) {
publish("Number of iterations: " + i);
//Do something
Thread.sleep(3500);
}
return null;
}
#Override
protected void process(List<String> chunks) {
String chunk = chunks.get(0);
label.setText(chunk);
}
#Override
protected void done() {
label.setText("Done.");
}
};
JButton button = new JButton("Start");
button.addActionListener(e -> worker.execute());
add(button);
label = new JLabel("Nothing yet.");
add(label);
setSize(400, 400);
setLocationByPlatform(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new WorkerExample().setVisible(true);
});
}
}
You can experiment with these in order to understand how a SwingWorker works, but I strongly recommend you to read concurrency in Swing before doing that.
Related
Should I create swing elements into EDT?
I got the concurrency problems with editing non thread-safe graphics elements, but I'm creating them, they aren't shown yet, and if they are a lot or they take some time to be allocated that would freeze the GUI, doesn't it?
Here an example where I use EDT to display but not to create my GUI structure:
public class Launcher {
private final SwingWorker worker;
private final JFrame frame;
private final JLabel label;
private final JProgressBar progressBar;
public Launcher() {
// init user interface
frame = new JFrame();
JPanel panel = new JPanel(new BorderLayout());
label = new JLabel("Launching...", SwingConstants.CENTER);
progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
panel.add(label, BorderLayout.CENTER);
panel.add(progressBar, BorderLayout.PAGE_END);
initUI(panel);
worker = new LauncherWorker(this);
worker.addPropertyChangeListener((PropertyChangeListener)this);
}
private void initUI(final Component panel) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
DirectaChatLauncher.this.initUI(panel);
} //run()
});
return;
}
Container contentPane = frame.getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(panel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
int progress = (Integer) evt.getNewValue();
progressBar.setValue(progress);
}
}
private void setProgression(final String msg) {
label.setText(msg);
}
class LauncherWorker extends SwingWorker<Boolean, String> {
private final Launcher LAUNCHER;
public LauncherWorker(Launcher launcher) {
super();
LAUNCHER = launcher;
}
#Override
protected Boolean doInBackground() throws Exception {
setProgress(0);
publish("Started");
...
setProgress(100);
publish("Launched");
Thread.sleep(1000);
return Boolean.TRUE;
}
#Override
protected void process(List<String> chunks) {
LAUNCHER.setProgression(chunks.get(0));
}
#Override
public void done() {
LAUNCHER.done();
}
}
}
is it fine since the elements weren't displayed yet? or should I move all into initUI()?
In the Swing separable model architecture, a view component listens to its model. Because a view may respond arbitrarily to events generated by model updates, the corresponding model must also be updated on the EDT. You can mitigate latency via one of two basic approaches:
Use EventQueue.invokeLater() from a separate thread, as shown here.
Use SwingWorker to obtain the additional benefits enumerated here.
Swing Threading Policy states:
In general Swing is not thread safe. All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.
Typical Swing applications do processing in response to an event generated from a user gesture. For example, clicking on a JButton notifies all ActionListeners added to the JButton. As all events generated from a user gesture are dispatched on the event dispatching thread, most developers are not impacted by the restriction.
Where the impact lies, however, is in constructing and showing a Swing application. Calls to an application's main method, or methods in Applet, are not invoked on the event dispatching thread. As such, care must be taken to transfer control to the event dispatching thread when constructing and showing an application or applet. The preferred way to transfer control and begin working with Swing is to use invokeLater. The invokeLater method schedules a Runnable to be processed on the event dispatching thread.
The code snippet below sets text in a JLabel, which is added to a JPanel, which is attached to a JFrame. No matter what I do though (such as repaint(), revalidate(), etc) I cannot get the UI to update the text until the Action Listener is done.
I have never had this problem before, possible because I have never had to have several things happen in a single firing of Action Listener. What am I missing?
TL;DR Why does the following not update the text on the screen until it has finished firing the Action Listener, even if I put in repaint() after each listPanel.add()?
final JFrame guiFrame = new JFrame();
final JPanel listPanel = new JPanel();
listPanel.setVisible(true);
final JLabel listLbl = new JLabel("Welcome");
listPanel.add(listLbl);
startStopButton.addActionListener(new ActionListener(){#Override public void actionPerformed(ActionEvent event){
if(startStopButton.getText()=="Start"){
startStopButton.setVisible(false);
listPanel.remove(0);
JLabel listLbl2 = new JLabel("Could not contact”);
listPanel.add(listLbl2);
JLabel listLbl2 = new JLabel("Success”);
listPanel.add(listLbl2);
}
}
guiFrame.setResizable(false);
guiFrame.add(listPanel, BorderLayout.LINE_START);
guiFrame.add(startStopButton, BorderLayout.PAGE_END);
//make sure the JFrame is visible
guiFrame.setVisible(true);
EDIT:
I attempted to implement SwingWorker, but still the interface is not updating until the action interface finishes firing. Here is my SwingWorker code:
#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();
}
}
Basically, SwingWorker (as I implemented it) is not properly updating the JPanel and JFrame. If I try to do the repaint in the "done()", they are not updated either. What am I missing?
Additionally, as soon as the JOptionPane displays itself, no more panels can be added to my jframe. I am unsure what is causing that either.
The action listener is being executed on the Event Dispatch Thread. For tasks like that, consider using a SwingWorker.
This would allow you to process your logic without blocking the updates (and thus the repaints) of the JFrame.
At a high level, this is what I mean:
startStopButton.addActionListener(new ActionListener(){#Override public void actionPerformed(ActionEvent event){
if(startStopButton.getText()=="Start"){
// Start SwingWorker to perform whatever is supposed to happen here.
}
You can find some information on how to use SwingWorker here, should you need it.
When a user clicks a button, a long task of approximately 10 seconds will run. During this time I want to show a progress bar to the user. But the main thread has to wait for the worker thread to finish because the worker thread will set a variable that the main thread will use. If I don't wait the worker thread I will get a NullPointerException when using the variable. So after the worker thread finishes, I will also close the progress bar dialog.
When I wait for the worker thread using join() the progress bar dialog shows (interestingly without the progress bar though) and hangs there.
Thread runnable = new Thread() {
public void run() {
try {
System.out.println("thread basladi");
threadAddSlaveReturnMessage = request.addSlave(
ipField.getText(), passField.getText(),
nicknameField.getText());
System.out.println("thread bitti");
} catch (LMCTagNotFoundException e) {
e.printStackTrace();
}
}
};
Thread runnable_progress = new Thread() {
public void run() {
JTextArea msgLabel;
JDialog dialog;
JProgressBar progressBar;
final int MAXIMUM = 100;
JPanel panel;
progressBar = new JProgressBar(0, MAXIMUM);
progressBar.setIndeterminate(true);
msgLabel = new JTextArea("deneme");
msgLabel.setEditable(false);
panel = new JPanel(new BorderLayout(5, 5));
panel.add(msgLabel, BorderLayout.PAGE_START);
panel.add(progressBar, BorderLayout.CENTER);
panel.setBorder(BorderFactory.createEmptyBorder(11, 11, 11, 11));
dialog = new JDialog(Frame.getFrames()[0], "baslik", true);
dialog.getContentPane().add(panel);
dialog.setResizable(false);
dialog.pack();
dialog.setSize(500, dialog.getHeight());
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setAlwaysOnTop(false);
dialog.setVisible(true);
msgLabel.setBackground(panel.getBackground());
}
};
runnable.start();
System.out.println("runnable start");
runnable_progress.start();
System.out.println("progress start");
runnable.join();
System.out.println("runnable join");
runnable_progress.join();
System.out.println("progress join");
if (threadAddSlaveReturnMessage.equalsIgnoreCase("OK")) {
fillInventoryTable(inventoryTable);
JOptionPane.showMessageDialog(this, messages.getString("centrum.addslavepanel.SUCCESS"), null, JOptionPane.INFORMATION_MESSAGE);
}
"progress join"
doesn't get printed.
You can use a SwingWorker here. A short example :
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.godel.nio;
import java.awt.BorderLayout;
import java.util.List;
import javax.swing.*;
/**
*
* #author internet_2
*/
public class Test {
public static void main(String[] args) {
new Test().doJob();
}
public void doJob() {
JTextArea msgLabel;
JProgressBar progressBar;
final int MAXIMUM = 100;
JPanel panel;
progressBar = new JProgressBar(0, MAXIMUM);
progressBar.setIndeterminate(true);
msgLabel = new JTextArea("deneme");
msgLabel.setEditable(false);
panel = new JPanel(new BorderLayout(5, 5));
panel.add(msgLabel, BorderLayout.PAGE_START);
panel.add(progressBar, BorderLayout.CENTER);
panel.setBorder(BorderFactory.createEmptyBorder(11, 11, 11, 11));
final JDialog dialog = new JDialog();
dialog.getContentPane().add(panel);
dialog.setResizable(false);
dialog.pack();
dialog.setSize(500, dialog.getHeight());
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setAlwaysOnTop(false);
dialog.setVisible(true);
msgLabel.setBackground(panel.getBackground());
SwingWorker worker = new SwingWorker() {
#Override
protected void done() {
// Close the dialog
dialog.dispose();
}
#Override
protected void process(List chunks) {
// Here you can process the result of "doInBackGround()"
// Set a variable in the dialog or etc.
}
#Override
protected Object doInBackground() throws Exception {
// Do the long running task here
// Call "publish()" to pass the data to "process()"
// return something meaningful
return null;
}
};
worker.execute();
}
}
Edit : "publish()" should be called in "doInBackground()" to pass the data to "process()".
you have the issue with Concurency is Swing, your GUI is visible after all thread are done
is possible to moving with JProgressBar (I'm talking about you code) but you have to
create and show JDialog, create once time and reuse this container
then to start Thread,
better could be from Runnable#Thread
output to the Swing GUI must be wrapped into invokeLater()
this is exactly job for using SwingWorker and with PropertyChangeListener
As the previous answers already mentioned, SwingWorker is the way to go, if use want to use concurrency with Swing.I found this SwingWorker and ProgressBar tutorial quite useful in understanding how it all works together.
Coming back to your actual problem: I assume you use a GUI, because you stated the user has to click a button. The question is, what is your "main thread" doing? Does it really have to run all the time? Doesn't look like it. Because you said the thread needs a variable which is set by another thread, which is a result of a user interaction. In short: Why does it need to run if it's dependent on a user interaction anyway? The usual way would be to first get all the data you need and then run the calculations or whatever. In your case, either run everything in a single background thread(set the variable first then do the rest), started by the ActionListener of your button or run the other thread after the thread where you set the variable has completed.You could for example use the method done(), provided by SwingWorker, to launch the next task. Or if you really have to, you could wait in a loop for task.isDone()to return true. But don't forget to check for isCancelled() too.Anyway, I think you should rethink your design. Because what I can see from the limited information provided looks overly complicated to me.
My code as below is not working, can anyone tell me why? Please also correct my code, I am very new to Java. Besides that, I am searching for the "loading panel component", something like ProgressMonitor but maybe more attractive and which animates better. Please suggest me if anyone has used such things before.
public class Main extends JFrame {
private JPanel contentPane;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main frame = new Main();
frame.setVisible(true);
ProgressMonitor pm = new ProgressMonitor(frame, "Loading...",
"waiting...",
0, 100000);
for (int i = 0 ; i < 100000 ; i ++){
pm.setProgress(i);
pm.setNote("Testing");
System.out.println(i);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
}
As #Mark Rotteveel already indicated, you are keeping the EDT (Event Dispatch Thread) occupied. The tutorial on 'How to show progress bars/monitors' contains valuable information and code samples.
But basically it comes down to moving your calculations to a worker Thread (e.g. using a SwingWorker), and showing the ProgressMonitor on the EDT. It is up to the worker thread to indicate to the ProgressMonitor what progress has already been made.
And here is a direct link to the sample code of that tutorial which clearly shows how the work is done in the SwingWorker extension (the Task class in that example), and how the ProgressMonitor gets updated by adding a PropertyChangeListener to the SwingWorker, where the listener passes the progress to the ProgressMonitor.
I would also suggest to read the Concurrency in Swing tutorial which contains more information on how to handle Threads in combination with Swing, and why you can't/shouldn't do heavy calculations on the EDT
In Swing, the event-thread is what modifies and updates the GUI. You are keeping the event-thread busy with that for-loop and sleep, so it cannot update the GUI. All things you do on the event-thread should be short-lived. Everything else should be moved off the event-thread.
So you need to move that for-loop out of the event-thread.
1. Consider this as the rule of thumb, UI work on UI thread, and Non-UI work on Non-UI thread.
2. Event Dispatcher Thread (EDT) is the UI thread here, and so you should keep your Non-UI process intensive work on a separate thread OUT of the EDT.
3. You can do this in 2 ways.....
i. Create a separate Thread to do this.
ii. Use SwingWorker to synchronize the Non-UI and the UI thread.
4. Always keep the main() method only for making the JFrame visible in the EDT.
eg:
public static void main(String[] args){
EventQueue.invokeLater(new Runnable(){
public void run(){
myframe.setVisible(true);
}
}
}
Take a look at this site for the working example of SwingWorker:
http://www.kodejava.org/examples/381.html
So what I want to do is that when I press button JTextField text starts to update to new value every 3 seconds. I have tried Thread sleep metod, but it freezes whole program for the sleep time and after it is over textfields gets the latest input. So here is better explained example of what i am trying to do.
I press the JButton which puts the numbers in JTextFiel every 3 seconds as long as there is available values. I dont want it to append new text, just replace old with new. Anyone got ideas how I can do that? Thanks in advance.
You should use a javax.swing.Timer.
final Timer updater = new Timer(3000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// update JTextField
}
});
JButton button = new JButton("Click me!");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
updater.start();
}
});
The Timer will not block the Event Dispatch Thread (like Thread.sleep does) so it won't cause your program to become unresponsive.
You can't sleep in the EDT. You can either use a swingworker (better solution) or do something like this:
//sleep in new thread
new Thread (new Runnable() {
public void run() {
Thread.sleep(3000);
//update UI in EDT
SwingUtilities.invokelater(new Runnable() {
public void run() {updateYourTextHere();}
});
}
}).start();
You need to get 'the work' done on a separate thread. See some of the answers here: Java GUI Not changing