JProgressBar not working properly - java

So my JProgressBar I have set up doesn't work the way I want it. So whenever I run the program it just goes from 0 to 100 instantly. I tried using a ProgressMonitor, a Task, and tried a SwingWorker but nothing I tried works.
Here is my program:
int max = 10;
for (int i = 0; i <= max; i++) {
final int progress = (int)Math.round(
100.0 * ((double)i / (double)max)
);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
jProgressBar2.setValue(progress);
}
});
}
#MadProgrammer Here is my attempt at making a swing worker and writing each name to the document and updating the progress bar. The program gets to around 86 percent and stops, never creating the finished document. The program creates a blank document. Here are the two methods first is the SwingWorker object I made:
public class GreenWorker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
int max = greenList.size();
XWPFParagraph tmpParagraph;
XWPFRun tmpRun;
FileInputStream file =
new FileInputStream(location + "GreenBandList.docx");
gDocument = new XWPFDocument(OPCPackage.open(file));
for (int i = 0; i < max; i++) {
tmpParagraph = gDocument.getParagraphs().get(0);
tmpRun = tmpParagraph.createRun();
if (greenList.get(i).length() == 1) {
tmpRun.setBold(true);
tmpRun.setText(greenList.get(i));
tmpRun.setBold(false);
} else {
tmpRun.setText(greenList.get(i));//Write the names to the Word Doc
}
int progress = Math.round(((float) i / max) * 100f);
setProgress(progress);
}
return null;
}
}
And here is the code for the button that starts it and has my property change event.
private void GenerateGreenList() throws IOException, InterruptedException {
//Need to fix the bug that removes the Letter Header in Yellow Band list
//********************************************************************\\
//Delete the old list and make a new one
File templateFile = new File(location + "\\backup\\GreenTemplate.docx");
FileUtils.deleteQuietly(new File(location + "GreenBandList.docx"));
FileUtils.copyFile(templateFile, new File(location +
"GreenBandList.docx"));
//Get the New Entries
String[] entries = jTextPane3.getText().split("\n");
for (String s : entries) {
if (s != null) {
greenList.add(s);
}
}
//Resort the list
Collections.sort(greenList);
//Write the names to the document
GreenWorker worker = new GreenWorker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
jProgressBar2.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
if (worker.isDone()) {
try {
gDocument.write(new FileOutputStream(new File(location + "GreenBandList.docx")));
////////////////////////////////////////////////////////////
} catch (IOException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
JOptionPane.showMessageDialog(null, "Green Band List Created!");
jProgressBar2.setValue(0);
}
}
I used the property change listener from one of your other posts but I don't really understand what the one you wrote does or what it does in general?

Swing is a single threaded environment, that is, there is a single thread which is responsible for processing all the events that occur within the system, including repaint events. Should anything block this thread for any reason, it will prevent Swing from processing any new events, including, repaint events...
So all this ...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex); }
jProgressBar2.setValue(progress);
}
});
Is constantly pausing the Event Dispatching Thread, preventing it from actually doing any updates (or at least spacing them randomly)...
It's also likely that your outer loop is been run from within the context of the EDT, meaning that until it exists, nothing in the Event Queue will be processed. All your repaint requests will be consolidated down to a single paint request and voila, instant filled progress bar...
You really should use a SwingWorker - I know you said you tried one, but you've not shown any code as to your attempt in this regards, so it's difficult to know why it didn't work, however...
SwingWorker and JProgressBar example
SwingWorker and JProgressBar example
SwingWorker and JProgressBar example
SwingWorker and JProgressBar example
SwingWorker and dual welding JProgressBar example
SwingWorker and JProgressBar example
And forgive me if we haven't said this a few times before :P

You are evoking Thread.sleep inside the EvokeLater which means that it is running on another thread than your for loop. i.e., your for loop is completing instantaneously (well, however long it takes to loop from 1 to 100, which is almost instantaneously).
Move Thread.sleep outside of EvokeLater and it should work as you intend.
int max = 10;
for (int i = 0; i <= max; i++) {
final int progress = (int)Math.round(
100.0 * ((double)i / (double)max)
);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
jProgressBar2.setValue(progress);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(BandListGenerator.class.getName()).log(Level.SEVERE, null, ex);
}
}
Edit: agree with #MadProgrammer. It appears this is just an illustrative question, but you should make sure whatever you're trying to accomplish here you use a SwingWorker for.

Related

Performance issue displaying JFreeChart points, when running in the EDT

Background:
A well-known Swing best-practice requirement is that code that
interacts with the Swing framework must also execute in EDT (Event
Dispatch Thread).
I thus changed my code to have my JFreeChart-based updates to run in EDT.
However, a complete chart display task that usually took about 7 minutes to finish on a “normal” thread, become a several hours task when running in EDT!
What am I doing wrong? Did I misunderstood the Swing Concurrency lesson? Do I really have to run org.jfree.data.time.TimeSeries.addOrUpdate(date, double) inside EDT?
Please advise!
Details:
Clicking a Swing GUI button, my program triggers a time-consuming task.
Basically, it reads a (large) file with pair-values (date, double) and then shows them by using the JFreeChart framework.
Because this is a time-consuming task, while reading and displaying data, a JProgreessBar shows user the progress status in foreground, while the chart is updated in background (user is still able to visually see every chart update, behind the progress bar).
This worked fine, until I decided to review the code to have my chart data being updated and displayed inside Swing EDT. Then, a complete task that usually took about 7 minutes to finish, started to take several hours to complete!
Here’s the list of threads I’m using:
1) Since the task is triggered by a Swing Button Listener, it is running in EDT. The JProgressBar is also running in this same thread;
2) While showing the JProgressBar, a second (“normal”) thread is created and executed in the background. This is where the heavy work is done.
It includes the update of the JProgressBar status on the other thread (by calling JProgressBar.setvalue()) and the update of my JFreeChart chart (by calling TimeSeries.addOrUpdate(date, double), which automatically updates a org.jfree.chart.ChartPanel).
Updating the chart in my second (“normal”) thread usually took about 7 minutes to finish. Without any noticeable issue.
However, knowing that most Swing object methods are not "thread safe" and ChartPanel is just a Swing GUI component for displaying a JFreeChart object, I decided to run my chart update code TimeSeries.addOrUpdate(date, double) inside EDT.
Still running in my second “normal” thread, I tested with the following asynchronous code:
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
TimeSeries.addOrUpdate(date, double);
}
});
but I realized my JProgressBar would reach 100% much before the chart was updated.
I guess this was expected as displaying chart data is much slower than getting and processing the data.
I then tried following synchronous code:
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
TimeSeries.addOrUpdate(date, double);
}
});
} catch (InvocationTargetException | InterruptedException e) {
e.printStackTrace();
}
And this is where I found the performance issue: now a complete task that used to take about 7 minutes to finish, started to take hours to complete!
So, my question is:
What am I doing wrong? Did I misunderstood the Swing Concurrency lesson? Do I really have to run TimeSeries.addOrUpdate(date, double) inside EDT?
Please advise!
UPDATE:
The complete code would be too large to show here, but you can find a code snapshot below.
Perhaps, the only thing noticeable about the code is that I use Reflection. This is because I use a generic ProgressBar Class that invokes in background whatever class I send it as an argument (though this is not clearly shown in the snapshot below).
//BUTTON LISTENER (EDT)
public void actionPerformed(ActionEvent arg0) {
new Process_offline_data();
}
public Process_offline_data() {
//GET DATA
String[][] data = get_data_from_file();
//CREATE PROGRESS BAR
int maximum_progressBar = data.length;
JProgressBar jpb = init_jpb(maximum_progressBar);
//JDIALOG MODAL WINDOW
JDialog jdialog = create_jdialog_window(jpb);
Object class_to_invoke_obj = (Object) new Show_data_on_chart();
String main_method_str = "do_heavy_staff";
Runnable r = new Runnable() {
public void run() {
//REFLECTION
Method method = null;
try {
method = class_to_invoke_obj.getClass().getDeclaredMethod(main_method_str, JProgressBar.class, String[][].class);
} catch (NoSuchMethodException | SecurityException e1) {
e1.printStackTrace();
jdialog.dispose(); //UNBLOCKS MAIN THREAD
return;
}
try {
method.invoke(class_to_invoke_obj, jpb, data);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
e1.printStackTrace();
jdialog.dispose(); //UNBLOCKS MAIN THREAD
return;
}
//----------------
jdialog.dispose(); //UNBLOCKS MAIN THREAD
}
};
new Thread(r).start();
//----------------
//THIS IS STILL EDT
jdialog.setVisible(true); //BLOCKS HERE UNTIL THE THREAD CALLS jdialog.dispose();
}
public class Show_data_on_chart {
public void do_heavy_staff(JProgressBar jpb, String[][] data) {
TimeSeries time_series = get_TimeSeries(); //JFreeChart datamodel
int len = data.length;
for (int i=0; i<len; i++) {
jpb.setValue(i+1);
Millisecond x_axys_millisecond = convert_str2date(data[i][0]);
Double y_axys_double = convert_str2double(data[i][1]);
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
//AUTOMATICALLY UPDATES org.jfree.chart.ChartPanel
time_series.addOrUpdate(x_axys_millisecond, y_axys_double);
}
});
} catch (InvocationTargetException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
This is how i solved the problem of updating the chart.
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYSeries;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.jfree.chart.plot.XYPlot;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
public class App extends ApplicationFrame {
XYSeries sin = new XYSeries("Sin");
public App(String applicationTitle, String chartTitle) {
super(applicationTitle);
JFreeChart xylineChart = ChartFactory.createXYLineChart(chartTitle, "X", "Y", new XYSeriesCollection(sin),
PlotOrientation.VERTICAL, false, true, false);
ChartPanel chartPanel = new ChartPanel(xylineChart);
chartPanel.setPreferredSize(new java.awt.Dimension(560, 367));
final XYPlot plot = xylineChart.getXYPlot();
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false);
plot.setRenderer(renderer);
setContentPane(chartPanel);
}
public Runnable r = new Runnable() {
double x, y;
int i;
public void run() {
int steps = 69999;
for (i = 0; i < steps; i++) {
//sample plot data
x = Math.PI * 2.0 * 10.0 / ((double) steps) * ((double) i);
y = Math.sin(x);
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if ((i % 1000) == 0) {
//adding data and redrawing chart
sin.addOrUpdate(x, y);
} else {
//adding point without redrawing of the chart
sin.add(x, y, false);
}
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//redrawing chart if all data loaded
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
sin.fireSeriesChanged();
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
public Runnable rupdate = new Runnable() {
public void run() {
while (true) {
//redrawing chart
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
sin.fireSeriesChanged();
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//waiting for next update
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
public static void main(String[] args) {
final App chart [] = new App[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
chart[0] = new App(null, null);
chart[0].pack();
RefineryUtilities.centerFrameOnScreen(chart[0]);
chart[0].setVisible(true);
}
});
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread job = new Thread(chart[0].r);
job.start();
Thread job2 = new Thread(chart[0].rupdate);
job2.start();
}
}
The above code includes two solutions. You can use either of them or both. Chart can be updated during data feeding. For example every 100th point and after last poit. Eventually you can make external thread that updates chart after some time. I have used updateAndWait every time instead of updateLater.
In your code do not use reflections like that. You should make interface. For example:
public interface IHardWork {
public void do_heavy_staff(JProgressBar jpb, String[][] data);
}
and implement it on every object that do the work:
public class Show_data_on_chart implements IHardWork {
public void do_heavy_staff(JProgressBar jpb, String[][] data) {
// TODO Auto-generated method stub
}
}
then use it:
IHardWork hwObj = new Show_data_on_chart();
hwObj.do_heavy_staff(jpb, data);
hwObj = new OtherHWObj();
hwObj.do_heavy_staff(jpb, data);
Eventualy You can make a base class for it and use polymorphism.

Trying To Delay In Java Multiple Times

Need To Do One Animation, Sleep 1000 Then Do The Next Sleep 1000 And So On, Instead It Sleeps For The Entire Time Then Plays All Animations At Once. No Idea What Im Doing.
Tried Timers, Running The Animation Before The tread.sleep And Using A While Loop Instead Of A For.
private void playLaunchAnimation()
{
final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fadein);
for(int i=0; i < buttons.size();i++)
{
try {
Thread.sleep(1000);
buttons.get(i).startAnimation(animation);
} catch (Exception e){
/*
main declares that it throws InterruptedException. This is an exception that sleep throws when another thread interrupts the current thread while sleep is active. Since this application has not defined another thread to cause the interrupt, it doesn't bother to catch InterruptedException.
*/
}
}
}
Hi, Make sure this code helps you
public static void main(String[] args) throws Exception {
for(int i=0; i < 10;i++) {
try {
Thread.sleep(1000);
System.out.println(i);
if(i==9) {
i=0;
}
} catch (Exception e){
System.out.println("error");
}
}
}
(Answer assumes Android which wasn't entirely clear in OP.)
This is somewhat of a lazy way to do it - but maybe get you thinking. It would
be more interesting to have the handler invoke the next handler so only one handler is declared - but that would be a little more work.
private void playLaunchAnimation() {
final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fadein);
for(int i=0; i < buttons.size();i++)
{
// Create a handler on the UI thread to execute after a delay.
// The delay is a function of loop index.
//
// It may be necessary to declare buttons final - but your
// OP did not list where it is defined.
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
#Override
public void run() {
buttons.get(i).startAnimation(animation);
}
}, ((i+1)*1000));
}
}
References:
How to call a method after a delay in Android
Android basics: running code in the UI thread
It sounds like you are calling Thread.Sleep from the User Interface thread. This will ultimately result in the entire User Interface freezing up for the duration of the sleep. What you really want is to launch sleep from a background thread.
For example:
AsyncTask.execute(new Runnable() {
#Override
public void run() {
for(int i=0; i < buttons.size();i++)
{
try {
Thread.sleep(1000);
activity.runOnUiThread(new Runnable() {
buttons.get(i).startAnimation(animation);
});
} catch (Exception e){}
}
});
Another way you could do this using post delay:
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
buttons.get(i).startAnimation(animation);
new android.os.Handler().postDelayed(this, 1000);
}
},
1000);

java swingworker thread to update main Gui

hi id like to know whats the best way to add text to a jtextarea from a swingworkerthread, ive created another class which a jbutton calls by Threadsclass().execute();
and the thread runs in parallel fine with this code
public class Threadsclass extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
for(int x = 0; x< 10;x++)
try {
System.out.println("sleep number :"+ x);
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(eftcespbillpaymentsThreads.class.getName()).log(Level.SEVERE, null, ex);
}
throw new UnsupportedOperationException("Not supported yet.");
}
}
now what id like to do is add the value of x to the text area on the main gui, any ideas much appreciated.
There is an excellent example from the JavaDocs
class PrimeNumbersTask extends
SwingWorker<List<Integer>, Integer> {
PrimeNumbersTask(JTextArea textArea, int numbersToFind) {
//initialize
}
#Override
public List<Integer> doInBackground() {
List<Integer> numbers = new ArrayList<Integer>(25);
while (!enough && !isCancelled()) {
number = nextPrimeNumber();
numbers.add(number);
publish(number);
setProgress(100 * numbers.size() / numbersToFind);
}
return numbers;
}
#Override
protected void process(List<Integer> chunks) {
for (int number : chunks) {
textArea.append(number + "\n");
}
}
}
JTextArea textArea = new JTextArea();
final JProgressBar progressBar = new JProgressBar(0, 100);
PrimeNumbersTask task = new PrimeNumbersTask(textArea, N);
task.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
System.out.println(task.get()); //prints all prime numbers we have got
Take a look at publish and process
The underlying intention is that you need to update the UI from only within the Event Dispatching Thread, by passing the data you want to updated to the UI via the publish method, SwingWorker will call process for you within the context of the EDT
Within doInBackground(), use publish(V... chunks) to send data to process(List<V> chunks).
See How SwingWorker works.

How to validate JLabel in Swing while using FTPClient

I want to update a JLabel each time with the name of the file which I am downloading using FTPClient. I tried repaint(), validate(), revalidate(), first invalidate() and immediately validate()/revalidate() but still nothing is working.
My Code goes as follows:
if(ae.getActionCommand()=="Download"){
int[] row_indexes=table.getSelectedRows();
notifylb.setText("Downloading files");
this.validate();
for(int i=0;i<row_indexes.length;i++)
{
String fn=table.getValueAt(row_indexes[i], 0).toString();
notifylb.setText("Downloading: "+fn); // fn contains filename
this.validate();
this.downloadFtpfile(fn);
}
notifylb.setText("SUCCESSFULLY DOWNLOADED FILE(s) !");
this.validate();
}
Suggestions:
Don't use == to compare Strings. Instead use the equals(...) or equalsIgnoreCase(...) methods. The == operator returns true if the two String objects are the same, but this isn't what matters to you, but rather you want to check if both Strings hold the same characters in the same order, and this is what the two methods above check.
You're currently downloading your files on the Swing event dispatch thread or EDT, and this will not only prevent your JLabel from updating but will also cause your GUI to freeze since this thread is responsible for drawing all Swing graphics including its own components, and for Swing interaction with the user.
Calling repaint(), revalidate(), invalidate(), etc... will do nothing to solve this.
To solve this, do the downloading or any long-running process in a background thread. One way to do this is by creating a new Thread, loading it with a Runnable, and calling start, but there's a better way that is tailor made for Swing GUI's and that's to create a SwingWorker object and do your background coding in its doInBackground() method. The SwingWorker tutorials can help you figure out how to do this, and if you get stuck in your attempt, please come on back with your code.
You'll probably not want to mention your urgency as this often has the opposite effect intended. Please remember that we are all volunteers, that your urgency is truly your urgency not ours, and that no one likes to feel rushed or pressured to do something for someone else, especially volunteers.
Best of luck and welcome to stackoverflow.
Edit
Since you've seen an example of using plain Threads, I figured I'd post an example of what doing this with a SwingWorker object could look like:
if (ae.getActionCommand().equalsIgnoreCase("Download")) {
final int[] row_indexes = table.getSelectedRows();
notifylb.setText("Downloading files");
final List<String> fileNames = new ArrayList<String>();
for (int i = 0; i < row_indexes.length; i++) {
fileNames.add(table.getValueAt(row_indexes[i], 0).toString());
}
SwingWorker<String, String> downloadSwingWorker = new SwingWorker<String, String>(){
#Override
protected String doInBackground() throws Exception {
for (String fileName : fileNames) {
publish("Downloading: " + fileName);
downloadFtpfile(fileName);
}
return "SUCCESSFULLY DOWNLOADED FILE(s) !";
}
#Override
protected void process(List<String> chunks) {
for (String text : chunks) {
notifylb.setText(text);
}
}
#Override
protected void done() {
try {
String text = get();
notifylb.setText(text);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
downloadSwingWorker.execute();
}
Edit 2: corrected as per kleopatra's suggestion
#Hovercraft-Full-Of-Eels explain very clear, but if you need the code, here it is how to write it.
final JButton finalButton = button; // this is your button will trigger download
final JLabel finalLabel = finalLabel;
final JTable finalTable = table;
if(ae.getActionCommand().equals("Download"))
{
finalButton.setEnabled(false); //disable button, so user can not start it for twice until ftp finished.
Thread thread = new Thread(new Runnable()
{
#Override
public void run()
{
int[] row_indexes = finalTable.getSelectedRows();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("Downloading files");
}
});
for(int i = 0; i < row_indexes.length; i++)
{
final String fn = finalTable.getValueAt(row_indexes[i], 0).toString();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("Downloading: " + fn); // fn contains filename
}
});
this.downloadFtpfile(fn);
}
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("SUCCESSFULLY DOWNLOADED FILE(s) !");
finalButton.setEnabled(true); //enable the button
}
});
}
});
thread.start();
};

Swing invokeLater never shows up, invokeAndWait throws error. What can I do?

I have this code:
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
try {
dialog.handleDownload();
} catch (IOException io) {
io.printStackTrace();
}
}
});
} catch(Exception io) { io.printStackTrace(); }
in the handleDownload I'm reading an inputstream, calculating a progress bar's value, and setting it to that. So, when I click a button, a new JFrame opens up and does all the stuff I wrote above.
If I have the dialog.handleDownload by itself ( in no SwingUtilities method ), it freezes until the operation is finished. If I add it in a invokeLater it's closed very fast ( I can't see anything, and the operation is not finished ). If I add it in a invokeAndWait I get the invokeAndWait cannot be called from the event dispatcher thread error. What should I do?
It looks like you could make use of SwingWorker. This allows you to defer an expensive operation to a background thread (keeping your GUI responsive) and when the operation is finished, do some stuff to the GUI.
Edit: Example
Here's a bit more complex example that shows how to use the basics of SwingWorker but also how to publish/process intermediate results.
public static void main(String[] args) {
final int SIZE = 1024*1024; //1 MiB
//simulates downloading a 1 MiB file
final InputStream in = new InputStream() {
int read = 0;
public int read() throws IOException {
if ( read == SIZE ) {
return -1;
} else {
if ( read % 200 == 0 ) {
try { Thread.sleep(1); } catch ( InterruptedException e ) {}
}
read++;
return 5;
}
}
};
final JProgressBar progress = new JProgressBar(0, SIZE);
final JButton button = new JButton("Start");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
button.setText("Working...");
SwingWorker<byte[], Integer> worker = new SwingWorker<byte[], Integer>() {
#Override
protected byte[] doInBackground() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
for ( int read = -1; (read = in.read(buff)) != -1; ) {
baos.write(buff, 0, read);
publish(read);
}
return baos.toByteArray();
}
#Override
protected void process(List<Integer> chunks) {
int total = 0;
for ( Integer amtRead : chunks ) {
total += amtRead;
}
progress.setValue(progress.getValue() + total);
}
#Override
protected void done() {
try {
byte[] data = get();
button.setText("Read " + data.length + " bytes");
} catch (Exception e) {
e.printStackTrace();
}
}
};
worker.execute();
}
});
JFrame frame = new JFrame();
frame.setLayout(new BorderLayout());
frame.add(button, BorderLayout.NORTH);
frame.add(progress, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack(); frame.setVisible(true);
}
Edit: Changed the example to drive a progress bar as if a download were taking place.
If you are doing that in response to a button click, you are already in the event thread so invokeAndWait is actually going in the wrong direction.
You need to start a new thread to execute the handleDownload thread that is NOT a the event dispatch thread--BUT
When running in your new thread, be sure that any GUI updates use invokeAndWait or preferably invokeLater to get back to the EDT.
The simple rules to remember:
Any thread handed to you by Swing is the EDT, so do all the GUI stuff on it you want
Do ALL updates of GUI elements on the EDT (ONLY).
Do anything that takes a long time on a non-EDT thread (Start a new thread).
Use invokeLater to get back to the EDT from a non-EDT thread
You shouldn't be accessing your inputStream in the event thread. Spawn a new thread which actually does the bulk of the handleDownload() work, then make the last operation performed by that thread be to call SwingUtilities.invokeLater() with the code that actually shows and populates the dialog.
What does "handleDownload" do? Time consuming things should not be done in the event dispatcher thread. If something is consuming lots of CPU cycles in the event dispatcher thread, then the display will freeze until it's done. You are far better off in a case like that invoking a normal thread (not using SwingUtilities) to do the processing outside of the event dispatcher thread, and in that thread using SwingUtilities.invokeLater to send back notifications that things have changed (like updating a progress bar) at regular intervals.
What it sounds like you need is a SwingWorker. This will allow you to have the file download take place in a separate thread that doesn't bother the EDT.
Your code would look something like this:
class Downloader extends SwingWorker<String, Void> {
#Override
public String doInBackground() {
dialog.handleDownload();
return "done";
}
#Override
protected void done() {
try {
someLabel.setText(get());
} catch (Exception ignore) {
}
}
}

Categories