Hello stack exchangers,
I have a problem with progress bars in java Swing. I think my confusions arise from my poor understanding of threads and the Swing event queue (I don't know much about java Threads, and exactly what is happening on the AWTEventQueue, although I generally understand what multithreading is about).
The context is that a JButton is pressed to start a long calculation. Before the calculation starts, I make a progress bar in a JFrame, which I thought would be painted, but it isn't. The frame appears, but it is just grey. The button, in this example has "clickMe" written on it.
In the "clickMe" action listener, I first make and display a JFrame in a subtask which is "run" (I'm not clear on when this is scheduled TBH). Then, I call doTask() which is running in the same thread as the action listener (which I think is the AWTEventThread??). The doTask() runs, printing out numbers to the Console. Intermixed with the doTask() output are iteration counts of the progressbar (from when the action listener started makeProgressBar()).
So, from the output, it looks like both the progress bar is running and the AWTEventThread, but the value set in the JProgressBar GUI is never updated.
How can I change my code so that the GUI gets updated? I've tried understanding the JProgressBar tutorial and hunted around the web, but I think my problem is more a conceptual understanding of Java Tasks.
This is my code:
package problemclass;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
public class ProblemClass
{
void progressBarButtonClick()
{
JFrame buttonInAFrame = new JFrame();
JPanel buttonInAFramePanel = new JPanel();
JButton clickMe = new JButton("Click me!");
buttonInAFramePanel.add(clickMe);
clickMe.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
JFrame progBarFrame = makeProgressBar();
doTask();
progBarFrame.dispose();
}
});
buttonInAFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
buttonInAFrame.add(buttonInAFramePanel);
buttonInAFrame.pack();
buttonInAFrame.setVisible(true);
}
private void doTask()
{
for(int i = 0; i < 20000; i++)
{
if (i % 100 == 0)
{
System.out.println("TASK iteration " + i);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e) {}
}
}
}
private JFrame makeProgressBar()
{
JFrame progBarFrame = new JFrame();
JPanel progBarPanel = new JPanel();
JProgressBar progressBar = new JProgressBar();
progBarPanel.add(progressBar);
progressBar.setValue(0);
progressBar.setStringPainted(true);
progressBar.setIndeterminate(true);
new Thread(new Runnable()
{
public void run()
{
for (int i = 0; i <= 100; i++)
{
final int j = i;
System.out.println("Progress Iteration " + j);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
progressBar.setValue(j);
}
});
try
{
java.lang.Thread.sleep(100);
}
catch(Exception e) { }
}
}
}).start();
progBarFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
progBarFrame.add(progBarPanel);
progBarFrame.pack();
progBarFrame.setVisible(true);
return progBarFrame;
}
public static void main(String[] args)
{
EventQueue.invokeLater(() ->
{
new ProblemClass().progressBarButtonClick();
});
}
}
JFrame progBarFrame = makeProgressBar();
doTask();
Not sure exactly what you are trying to do.
The above code has two loops:
In the makePrgressBar() method you start a Thread and invoke SwingUtilities.invokeLater(…), to update the progress bar, which is correct.
but then in doTack() you start another loop. This time you don't start a Thread so the code is invoked on the EDT and since you use Thread.sleep, the EDT will sleep and the GUI will not repaint itself until the entire loop is finished.
I would suggest you get rid of the doTask() method since I don't know why you need two blocks of code that loop. Or if you really need it, then you also need to use a Thread and invokeLater(…).
Just like you, I recently did some work on progress bars and threading and went nuts until I realized that it is just so simple.In a nutshell this is the code I have when my button is clicked:
// Create 2 threads. One handles your GUI. Other does the task
Thread t1 = new Thread(new Runnable() {
#Override
public void run() {
// code goes here.
//In here I choose to hide the button, display the progress bar
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
#Override
public void run() {
// code goes here.
//In here I get the task done, then hide the progress bar
}
});
t2.start();
Works like a charm every time. Hope it helps!
Related
Bellow is the code for the simplest GUI countdown. Can the same be done in a shorter and more elegant way with the usage of the Swing timer?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class CountdownNew {
static JLabel label;
// Method which defines the appearance of the window.
public static void showGUI() {
JFrame frame = new JFrame("Simple Countdown");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("Some Text");
frame.add(label);
frame.pack();
frame.setVisible(true);
}
// Define a new thread in which the countdown is counting down.
public static Thread counter = new Thread() {
public void run() {
for (int i=10; i>0; i=i-1) {
updateGUI(i,label);
try {Thread.sleep(1000);} catch(InterruptedException e) {};
}
}
};
// A method which updates GUI (sets a new value of JLabel).
private static void updateGUI(final int i, final JLabel label) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
label.setText("You have " + i + " seconds.");
}
}
);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
}
Yes you SHOULD use a Swing Timer. You SHOULD NOT, use a util Timer and TimerTask.
When a Swing Timer fires the code is executed on the EDT which means you just need to invoke the label.setText() method.
When using the uitl Timer and TimerTask, the code DOES NOT execute on the EDT, which means you need to wrap your code in a SwingUtilities.invokeLater to make sure the code executes on the EDT.
And that is way using a Swing Timer is shorter and more elegant than your current approach, it simplifies the coding because to code is executed on the EDT.
You could make it a little more elegant by using Timer with an appropriate TimerTask.
Yes, use a timer. updateGUI would be the code for the timer task, but it will need some changes as you won't be able to pass in i for each call since you just get a run() method.
i know multithreading a bit but not in vast and i think the problem is of multithreading. I am calling a method to set label's text by invoking a new thread and leaving it blank after a specified time. I am getting the desired output every time but not only the place which i am going to show you by my piece of code. I am expecting that message should be set and disappeared after the specified time and the window should be minimized after that time. But what actually happening is when it is going to the other thread main thread execution starts and goes for sleep for 5 sec and the message is not appearing and after 5 sec window is getting minimized without showing the message which i am setting on the label.
(Main thread)
Validation.setMessageOnLabel("Username and password has been copied", jLabel15,1.5F);
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(PasswordManager.class.getName()).log(Level.SEVERE, null, ex);
}
setState(ICONIFIED);
validation.java (setMessageOnLabel())
static public void setMessageOnLabel(final String msg, final JLabel label, final float time)
{
new Thread(new Runnable() {
#Override
public void run() {
label.setText(msg);
try {
Thread.sleep((long) (time*1000));
} catch (InterruptedException ex) {
Logger.getLogger(PasswordManager.class.getName()).log(Level.SEVERE, null, ex);
}
label.setText("");
}
}).start();
}
Since you're calling setState() directly, I assume the first code snippet is part of a JFrame. In that case you're most probably sending the event dispatch thread to sleep for 5 seconds and thus prevent screen updates during that time.
Put the sleep into another thread or use a swing worker instead and call setState() on the EDT in the worker's callback method, since setState() is not labelled as thread-safe and calling it on a thread other than the EDT might result in unexpected behavior.
From the linked tutorial:
Some Swing component methods are labelled "thread safe" in the API specification; these can be safely invoked from any thread. All other Swing component methods must be invoked from the event dispatch thread. Programs that ignore this rule may function correctly most of the time, but are subject to unpredictable errors that are difficult to reproduce.
Don't use Thread.sleep(5000);, that block EDT.
For that purposes you can use swing Timer, examine next example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class TestFrame extends JFrame {
private JLabel lbl;
public TestFrame() {
init();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void init() {
lbl = new JLabel(" ");
JButton setText = new JButton("setText");
setText.addActionListener(getActionListener());
add(lbl);
add(setText,BorderLayout.SOUTH);
}
private ActionListener getActionListener() {
return new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
lbl.setText("wait...");
Timer t = new Timer(5000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
lbl.setText("");
setState(JFrame.ICONIFIED);
}
});
t.setRepeats(false);
t.start();
}
};
}
public static void main(String args[]) {
new TestFrame();
}
}
When dealing with Swing components you shuld not use threads like that. Launch your own SwingWorker instead.
public class MySwingWorker extends SwingWorker<Object, Object> {
#Override
public Object doInBackground() {
//your code here
//dont forget to repaint changed component or validate parent of it,
//if your text dont shows up.
return null;
}
}
you can also execute your own runnable via SwingUtilites
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//again your code here...
}
});
I am trying to upload a few files over the network. while uploading it will take an indeterminate time. so wish to display an indeterminate jprogressbar during the upload. The problem, however is that my jprogressbar and upload does not work simultaneously. I tried the upload in a separate thread while keeping the jprogressbar in the EDT. I tried a few different ways. some of them are:
1) implemented Runnable and in run() I uploaded the file. The progressbar was in the EDT all this time. (did not work.)
2) had two separate threads and put both upload and progressbar handling in each. (not working). the code for this is :
Thread oThread = new Thread(new Runnable() {
#Override
public void run()
{
progressBar.setIndeterminate(true);
progressBar.setVisible(true);
progressBar.validate();
}
});
Thread oThread1 = new Thread(new Runnable()
{
#Override
public void run() {
logger.info("Upload result from ***: "+ newport.upload(textbyte, wavbyte,xmlbyte, filename));
}
});
3) then I rewrote the entire thing and tried a different approach using Executor like this:
executor.execute(new Runnable() {
#Override
public void run() {
upload actions
SwingUtilities.invokeLater(new Runnable() {
progBar.setVisible(false);
});
}});
but none of these techniques worked. I am new to this and am wondering if all this has to be done to have a jprogressbar (indeterminate) displayed. I would like to know if there is a simpler and easier way to do this.
You aren't actually setting the value of the progress bar at any point in the code you have posted.
Additionally this looks like a good case for using SwingWorker, it will perform the work on a different thread and then call you back with progress. The good thing being those callbacks happen already on the Swing thread.
This SwingWorker example even includes setting a progress bar:
http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html
you must use setValue() for change progressbar
below code example for work with jporgressbar
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class Tets {
public static void main(String [] args) {
JFrame frame =new JFrame();
JPanel panel=new JPanel();
JProgressBar bar=new JProgressBar();
bar.setMaximum(100);
bar.setMinimum(0);
panel.add(bar);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
for(int i=0;i<100;i++){
bar.setValue(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
good luck
I currently have a java program with swing gui that lets the user choose various files (xsl-fo and xml) and generates PDFs using Render X. I have trying for a while to get a pop up JFrame to appear when a button is pressed, which would then show a progress bar, or label to keep the user informed of the progress. However when instantiating a new frame it will appear black, or without components, which then show after the processes have completed.
private void RunButtonActionPerformed(java.awt.event.ActionEvent evt) {
ExecutorService executor = Executors.newFixedThreadPool(8);
//for reach file to process)
for (int i = 0; i < count; i++) {
Runnable worker = new ProcessThreader(conf, i);
executor.execute(worker);
}
executor.shutdown();
JFrame PercentageFrame = new JFrame();
PercentageFrame.setVisible(true);
PercentageFrame.setSize(200, 200);
PercentageFrame.repaint();
while (!executor.isTerminated()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
NarrowOptionPane.errorMessage("Interrupted: ", ex.getMessage());
}
}
System.out.println("Complete");
}
The run button is located in a JPanel, which is located in a JFrame and the Main Frame is instantiated in the main method, and wrapped in the invoke later method
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new MainFrame("PDF Producer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setSize(710, 530);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
}
});
}
I'm fairly new to threading / executors and java swing, so go easy! Thanks
Code executed from within a listener is executed on the Event Dispatch Thread. So the Thread.sleep() is causing the EDT to sleep which means the GUI can't respond to events or repaint itself.
Read the section from the Swing tutorial on Concurrency In Swing for more information. One solution as described in the tutorial is to use a SwingWorker for the long running task and to publish results as they become available.
Your while loop block EDT, delete that and your code will be work.
See next example with ExecutorService and JProgressBar :
import java.awt.BorderLayout;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
public class Example extends JFrame {
private static JProgressBar progress;
public static void main(String[] args) {
final JFrame f = new JFrame();
progress = new JProgressBar();
progress.setStringPainted(true);
progress.setIndeterminate(true);
ExecutorService newCachedThreadPool = Executors.newFixedThreadPool(1);
for( int i =0; i<10;i++){
final int j = i;
Runnable r = new Runnable() {
#Override
public void run() {
progress.setString(j+"");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
newCachedThreadPool.submit(r);
}
f.getContentPane().add(progress,BorderLayout.CENTER);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
}
Here JFrame show number of Runnable which executed in ExecutorService. Also if you need to get result from your Runnables, try to use Callable instead of Runnable. With that when you submit Callable you get Future instance from which you can get result.
Read about Executors, Callable and Future.
I need to execute/display a series of events from a Arraylist to a JTextArea, however, each Event gets execute with different time. Following is a quick example of my goal:
public void start(ActionEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
jTextArea.append("Test" + "\n");
try
{
Thread.sleep(3000);
} catch (InterruptedException e1)
{
e1.printStackTrace();
}
jTextArea.append("Test1" + "\n");
}
});
}
So right now, "Test" and "Test1" display on JTextArea after whole execution is completed.
How do I make "Test" display first, then 3 secs later, display "Test1"
Thank u all in advance
invokeLater schedules the runnable to run on the Event Dispatch Thread. You shouldn't sleep within it or you will starve the dispatch thread. Try using a separate worker thread instead:
Thread worker = new Thread(new Runnable(){
public void run(){
jTextArea.append("Test" + "\n");
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
jTextArea.append("Test1" + "\n");
}
});
worker.start();
If your tasks are time/cpu intensive, then yes, definitely use a background thread to do this such as a SwingWorker object or a Runnable run in a Thread. If however what you need to do is to stagger the display of something and all you are looking for is the Swing equivalent of Thread.sleep(3000), then your best option is to use a Swing Timer. There is an excellent tutorial on how to use these which you can find here: http://download.oracle.com/javase/tutorial/uiswing/misc/timer.html
For example:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Fu extends JPanel {
private static final int TIMER_DELAY = 600;
protected static final int MAX_COUNT = 20;
private JTextArea jTextArea = new JTextArea(10, 10);
private JButton startBtn = new JButton("Start");
private Timer timer;
public Fu() {
startBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startAction(e);
}
});
add(new JScrollPane(jTextArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));
add(startBtn);
}
private void startAction(ActionEvent e) {
if (timer != null && timer.isRunning()) {
// prevent multiple instances of timer from running at same time
return;
}
timer = new Timer(TIMER_DELAY, new ActionListener() {
private int count = 0;
public void actionPerformed(ActionEvent e) {
if (count < MAX_COUNT) {
count++;
jTextArea.append("Test " + count + "\n");
} else {
jTextArea.append("Done! \n");
timer.stop();
timer = null;
}
}
});
timer.setInitialDelay(0);
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Foo");
frame.getContentPane().add(new Fu());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
As pointed out, this is a bad idea, as you will block the event thread.
However, understanding the reason for this is important as well. As you seem to know, all code that affects the state of Swing components needs to happen in the event handling thread (which is the reason why invokeLater and friends should always be used).
What is a bit less better known is that paining code also executes in the event handling thread. When your call to Thread.sleep is executing, it's not only blocking the event thread, it's also blocking any painting of components. This is why the full update appears to happen in one go -- the JTextArea is updated but it can't be repainted until your run method returns.
Lots of info available here: http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html