Java SwingWorker with JDialog showing JProgressBar during JDBC network operation - java

I have a frame which has a button, when it is pressed a JDialog with a progress bar is shown and some data is being fetched using jdbc driver (progress bar is being updated). I needed a cancel button, so I spent some time figuring out how to connect everything. It seems to be working, but I sincerely am not sure if this way is any good. If someone has some spare time please check this code and tell me if anything is wrong with it - mainly with the whole SwingWorker and cancellation stuff.
On my pc (linux) the unsuccessful network connection attempt (someNetworkDataFetching method) takes a whole minute to timeout, do I have to worry about the SwingWorkers which are still working (waiting to connect despite being cancelled) when I try to create new ones?
Note: you need mysql jdbc driver library to run this code.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test extends JFrame {
private JProgressBar progressBar = new JProgressBar();
private JLabel label = new JLabel();
private DataFetcherProgress dfp;
/**
* This class holds retrieved data.
*/
class ImportantData {
ArrayList<String> chunks = new ArrayList<>();
void addChunk(String chunk) {
// Add this data
chunks.add(chunk);
}
}
/**
* This is the JDialog which shows data retrieval progress.
*/
class DataFetcherProgress extends JDialog {
JButton cancelButton = new JButton("Cancel");
DataFetcher df;
/**
* Sets up data fetcher dialog.
*/
public DataFetcherProgress(Test owner) {
super(owner, true);
getContentPane().add(progressBar, BorderLayout.CENTER);
// This button cancels the data fetching worker.
cancelButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
df.cancel(true);
}
});
getContentPane().add(cancelButton, BorderLayout.EAST);
setLocationRelativeTo(owner);
setSize(200, 50);
df = new DataFetcher(this);
}
/**
* This executes data fetching worker.
*/
public void fetchData() {
df.execute();
}
}
class DataFetcher extends SwingWorker<ImportantData, Integer> {
DataFetcherProgress progressDialog;
public DataFetcher(DataFetcherProgress progressDialog) {
this.progressDialog = progressDialog;
}
/**
* Update the progress bar.
*/
#Override
protected void process(List<Integer> chunks) {
if (chunks.size() > 0) {
int step = chunks.get(chunks.size() - 1);
progressBar.setValue(step);
}
}
/**
* Called when worker finishes (or is cancelled).
*/
#Override
protected void done() {
System.out.println("done()");
ImportantData data = null;
try {
data = get();
} catch (InterruptedException | ExecutionException | CancellationException ex) {
System.err.println("done() exception: " + ex);
}
label.setText(data != null ? "Retrieved data!" : "Did not retrieve data.");
progressDialog.setVisible(false);
}
/**
* This pretends to do some data fetching.
*/
private String someNetworkDataFetching() throws SQLException {
DriverManager.getConnection("jdbc:mysql://1.1.1.1/db", "user", "pass");
// Retrieve data...
return "data chunk";
}
/**
* This tries to create ImportantData object.
*/
#Override
protected ImportantData doInBackground() throws Exception {
// Show the progress bar dialog.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
dfp.setVisible(true);
}
});
ImportantData data = new ImportantData();
try {
int i = 0;
// There is a network operation here (JDBC data retrieval)
String chunk1 = someNetworkDataFetching();
if (isCancelled()) {
System.out.println("DataFetcher cancelled.");
return null;
}
data.addChunk(chunk1);
publish(++i);
// And another jdbc data operation....
String chunk2 = someNetworkDataFetching();
if (isCancelled()) {
System.out.println("DataFetcher cancelled.");
return null;
}
data.addChunk(chunk2);
publish(++i);
} catch (Exception ex) {
System.err.println("doInBackground() exception: " + ex);
return null;
}
System.out.println("doInBackground() finished");
return data;
}
}
/**
* Set up the main window.
*/
public Test() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label.setHorizontalAlignment(SwingConstants.CENTER);
getContentPane().add(label, BorderLayout.CENTER);
// Add a button starting data fetch.
JButton retrieveButton = new JButton("Do it!");
retrieveButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fetchData();
}
});
getContentPane().add(retrieveButton, BorderLayout.EAST);
setSize(400, 75);
setLocationRelativeTo(null);
progressBar.setMaximum(2);
}
// Shows new JDialog with a JProgressBar and calls its fetchData()
public void fetchData() {
label.setText("Retrieving data...");
dfp = new DataFetcherProgress(this);
dfp.fetchData();
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// Use jdbc mysql driver
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return;
}
// Show the Frame
new Test().setVisible(true);
}
});
}
}

About the only thing I might do different is not use the SwingUtilities.invokeLater in the doInBackground method to show the dialog, but maybe use a PropertyChangeListener to monitor the changes to the state property worker.
I would also use the PropertyChangeListener to monitor the changes to the progress property of the worker. Instead of using publish to indicate the progression changes I would use the setProgress method (and getProgress in the PropertyChangeListener)
For example...java swingworker thread to update main Gui
I might also consider creating the UI on a JPanel and adding it to the JDialog rather then extending directory from JDialog as it would give the oppurtunity to re-use the panel in other ways, should you wish...

Related

How can I show progress bar using Swingworker?

This is my code snippet. On the click of button it executes the loading program in the background, but I am unable to fetch the details of the task in the progress bar. Can anyone please tell me what am I missing here?
The point is I don't want to include all the insertion code inside my doInBackground method.
public class ProgressBarDemo extends JPanel
implements ActionListener,
PropertyChangeListener {
private JProgressBar progressBar;
private JButton startButton;
private JTextArea taskOutput;
private Task task;
class Task extends SwingWorker<Void, Integer> {
#Override
protected void process(List<Integer> arg0) {
// TODO Auto-generated method stub
super.process(arg0);
for(int k:arg0)
System.out.println("arg is "+k);
setProgress(arg0.size()-1);
}
/*
* Main task. Executed in background thread.
*/
#Override
public Void doInBackground() throws Exception {
Random random = new Random();
int progress = 0;
//Initialize progress property.
setProgress(0);
Thread.sleep(100);
new LoadUnderwritingData().filesinfolder("D:\\files to upload\\");
System.out.println("records inserted are "+LoadData.records_count_inserted);
publish(LoadData.records_count_inserted);
/*
* while (progress < 100) { //Sleep for up to one second. try {
* Thread.sleep(random.nextInt(1000)); } catch (InterruptedException ignore) {}
* //Make random progress. progress += random.nextInt(10);
* setProgress(Math.min(progress, 100)); }
*/
return null;
}
/*
* Executed in event dispatching thread
*/
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
startButton.setEnabled(true);
setCursor(null); //turn off the wait cursor
taskOutput.append("Done!\n");
}
}
I see you aren't calling progressBar.setValue(progress);.
Also you'd need to call publish(currentInsertCount); after each inserted element.
In the process method you'd do:
// assuming you are passing the current insert count to publish()
for (int k : arg0){
System.out.println("arg is " + k);
progressBar.setValue(k);
}
However from what you posted now it is not really clear where processing even occurs.
Is it this line:
new LoadUnderwritingData().filesinfolder("D:\\files to upload\\");
If it is then you would have to pass some Callback to the filesinfolder("..."), so that it can update the progress.
Note: It might be easier to just do it in a plain new Thread instead of using the SwingWorker.
How I would do it with a plain Thread:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileFilter;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class ProgressBarDemo extends JPanel implements ActionListener {
private JProgressBar progressBar;
private JButton startButton;
private JTextArea taskOutput;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("ProgressBarDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setPreferredSize(new Dimension(500, 500));
frame.add(new ProgressBarDemo());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public ProgressBarDemo() {
progressBar = new JProgressBar(0, 100);
startButton = new JButton(">");
taskOutput = new JTextArea();
startButton.addActionListener(this);
setLayout(new BorderLayout());
add(startButton, BorderLayout.NORTH);
add(taskOutput, BorderLayout.CENTER);
add(progressBar, BorderLayout.SOUTH);
progressBar.setVisible(false);
}
// If you don't care about a small chance of the UI not updating properly then you can perform the updates directly instead of calling invokeLater.
// If you are compiling below Java 1.8, you need to convert Lambda expression to a Runnable and make the directory parameter final.
public void upload(File directory) {
// No need for invokeLater here because this is called within the AWT Event Handling
startButton.setEnabled(false);
progressBar.setVisible(true);
new Thread() {
#Override
public void run() {
taskOutput.append("Discovering files...\n");
// List<File> files = Arrays.asList(directory.listFiles()); //
// if you want to process both files and directories, but only
// in the given folder, not in any sub folders
// List<File> files = Arrays.asList(getAllFiles(directory)); //
// if you only want the files in that directory, but not in sub
// directories
List<File> files = getAllFilesRecursive(directory); // if you
// want all
// files
SwingUtilities.invokeLater(() -> {
taskOutput.append(" -> discovered " + files.size() + " files.\n");
progressBar.setMaximum(files.size());
taskOutput.append("Processing files...\n");
});
int processedCount = 0;
for (File file : files) {
try {
byte[] bytes = Files.readAllBytes(file.toPath());
// TODO: process / upload or whatever you want to do with it
} catch (Throwable e) {
// Same here, you may skip the invokeLater
SwingUtilities.invokeLater(() -> taskOutput.append("Failed to process " + file.getName() + ": " + e.getMessage() + "\n"));
e.printStackTrace();
} finally {
processedCount++;
final int prcCont = processedCount; // not necessary if invoking directly
SwingUtilities.invokeLater(() -> progressBar.setValue(prcCont));
}
}
SwingUtilities.invokeLater(() -> {
taskOutput.append(" -> done.\n");
startButton.setEnabled(true);
progressBar.setVisible(false);
});
}
}.start();
}
#Override
public void actionPerformed(ActionEvent e) {
upload(new File("C:\\directoryToUpload"));
}
/**
* Gets all normal files in the directory.
*/
public static File[] getAllFiles(File directory) {
return directory.listFiles(new FileFilter() {
#Override
public boolean accept(File pathname) {
return pathname.isFile();
}
});
}
/**
* Gets all normal files in the given directory and its sub directories.
*/
public static List<File> getAllFilesRecursive(File directory) {
List<File> result = new ArrayList<File>();
getAllFilesRecursive(directory, result);
return result;
}
private static void getAllFilesRecursive(File directory, List<File> addTo) {
if (directory.isFile()) {
addTo.add(directory);
} else if (directory.isDirectory()) {
File[] subFiles = directory.listFiles();
if (subFiles == null)
return;
for (File subFile : subFiles) {
getAllFilesRecursive(subFile, addTo);
}
}
}
}

Synchronized copying display with jProgressBar

I wanted to monitor the progress of my file getting copied from source to destination. I have used synchronized keyword but somehow it not working as i expect it to be, my logic might be wrong. I will be glad if you help me out.
Here is my Code.
public class Download extends javax.swing.JFrame {
int val=0;
private Timer t;
private ActionListener a;
/* Creates new form Download */
public Download() {
initComponents();
jProgressBar1.setValue(val);
a = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
if (jProgressBar1.getValue() < val)
jProgressBar1.setValue(jProgressBar1.getValue()+1);
else
t.stop();
}
};
}
public synchronized void copy(String source,String url)
{
try {
val+=25;
t=new Timer(200,a);
t.start();
FileInputStream fs = new FileInputStream(source);
FileOutputStream os = new FileOutputStream(url);
int b;
while ((b = fs.read()) != -1) {
os.write(b);
}
os.close();
fs.close();
} catch (Exception E) {
E.printStackTrace();
}
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
String url = null;
int returnValue = chooser.showDialog(null, "Select");
if (returnValue == JFileChooser.APPROVE_OPTION) {
url = chooser.getSelectedFile().getPath();
} else {
dispose();
}
JOptionPane.showMessageDialog(this,"Wait for Completion");
if(CB1.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Ele.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB1.getText()+".pdf"));
}
if(CB2.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Mech.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB2.getText()+".pdf"));
}
if(CB3.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Phy.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB3.getText()+".pdf"));
}
if(CB4.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Civil.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB4.getText()+".pdf"));
}
JOptionPane.showMessageDialog(this,"Completed");
try {
jProgressBar1.setValue(100);
Thread.sleep(3000);
} catch (InterruptedException ex) {
Logger.getLogger(Download.class.getName()).log(Level.SEVERE, null, ex);
}
System.exit(0);
}
}
Here I tried to implement a logic in such a that, whenever we call "copy" method it will copy the file from one location to another and before that it should run the timer method by which the progress on the jProgressBar is displayed. But unfortunately even after using synchronized it is not displaying the progress for each file.
The problem is you are blocking Swing's Event Dispatching Thread (EDT).
Swing does all drawing when the EDT is not busy responding to events. In this case jButton1ActionPerformed is not returning until all files have been copied. So although a Timer is started during each copy() call, the timers never get a chance to expire, because jButton1ActionPerformed has never returned.
In this case, you want to use a SwingWorker to copy the files in a background thread.
When you want to start copying the files:
start the timer in the main thread
create and start the SwingWorker.
open a model dialog to block further user actions (or otherwise disable the UI)
As the timer expires, your progress bar will advance, and be drawn.
When the SwingWorker is done() (which is executed on the EDT),
stop the timer
dismiss the dialog (or re-enable the UI)
Note: Do not create or access any UI items, or create/start/stop timers, from the background worker thread. These actions must only be performed on the EDT.
Rough example, showing disabling UI element, starting SwingWorker, publishing from the worker to show progress (which file is being download), enabling UI when the worker finishes.
File copy is faked using a 3 seconds sleep.
package progress;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class Download extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(Download::new);
}
private final JButton downloadBtn = new JButton("Start Download");
private final JProgressBar progressBar = new JProgressBar();
private final Timer timer = new Timer(200, this::timerTick);
Download() {
super("Download Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
setLocationByPlatform(true);
downloadBtn.addActionListener(this::startDownload);
add(downloadBtn, BorderLayout.PAGE_START);
progressBar.setStringPainted(true);
add(progressBar, BorderLayout.PAGE_END);
setVisible(true);
}
private void startDownload(ActionEvent evt) {
downloadBtn.setEnabled(false);
timer.start();
DownloadWorker worker = new DownloadWorker("File1", "FileB", "AnotherFile");
worker.execute();
}
private void timerTick(ActionEvent evt) {
progressBar.setValue(progressBar.getValue()+2);
}
private class DownloadWorker extends SwingWorker<Void, String> {
private final String[] files;
DownloadWorker(String ...files) {
this.files = files;
progressBar.setValue(0);
}
#Override
protected Void doInBackground() throws Exception {
for(String file : files) {
publish(file);
// Copy the file
Thread.sleep(3000); // Pretend copy takes a few seconds
}
return null;
}
#Override
protected void process(List<String> chunks) {
String file = chunks.get(chunks.size()-1); // Just last published filename
progressBar.setString("Downloading "+file + " ...");
}
#Override
protected void done() {
progressBar.setString("Complete");
progressBar.setValue(100);
timer.stop();
downloadBtn.setEnabled(true); // Re-enable UI
}
}
}

Why is my download progress bar firing the same event multiple times?

I'm practicing Swing and I coded a download progress bar to download an image when the user presses the "Start download" button. The download works. The problem is that in my Terminal, I can see that the same event (propertyChange) is being fired multiple times, the number of times increasing with every subsequent download. I've debugged my code with checkpoints, but I'm still not sure why this is happening.
To be more specific, in my Terminal I'm seeing something like
...100% completed
...100% completed
...100% completed
...100% completed
...100% completed
...100% completed
...100% completed
when I expect to see "...100% completed" only once. The number of "...100% completed" that is displayed accumulates with every download. I'm not sure if this is affecting the performance of my download, but I'm wondering why it's happening.
ProgressBar.java:
package download_progress_bar;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ProgressBar {
private JFrame frame;
private JPanel gui;
private JButton button;
private JProgressBar progressBar;
public ProgressBar() {
customizeFrame();
createMainPanel();
createProgressBar();
createButton();
addComponentsToFrame();
frame.setVisible(true);
}
private void customizeFrame() {
// Set the look and feel to the cross-platform look and feel
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception e) {
System.err.println("Unsupported look and feel.");
e.printStackTrace();
}
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
}
private void createMainPanel() {
gui = new JPanel();
gui.setLayout(new BorderLayout());
}
private void createProgressBar() {
progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true); // renders a progress string
}
private void createButton() {
button = new JButton("Start download");
}
private void addComponentsToFrame() {
gui.add(progressBar, BorderLayout.CENTER);
gui.add(button, BorderLayout.SOUTH);
frame.add(gui);
frame.pack();
}
// Add passed ActionListener to the button
void addButtonListener(ActionListener listener) {
button.addActionListener(listener);
}
// Get progress bar
public JProgressBar getProgressBar() {
return progressBar;
}
// Enable or disable button
public void turnOnButton(boolean flip) {
button.setEnabled(flip);
}
}
Downloader.java:
package download_progress_bar;
import java.net.*;
import java.io.*;
import java.beans.*;
public class Downloader {
private URL url;
private int percentCompleted;
private PropertyChangeSupport pcs;
public Downloader() {
pcs = new PropertyChangeSupport(this);
}
// Set URL object
public void setURL(String src) throws MalformedURLException {
url = new URL(src);
}
// Add passed PropertyChangeListener to pcs
public void addListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void download() throws IOException {
// Open connection on URL object
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Check response code (always do this first)
int responseCode = connection.getResponseCode();
System.out.println("response code: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
// Open input stream from connection
BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
// Open output stream for file writing
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("cat.jpg"));
int totalBytesRead = 0;
//int percentCompleted = 0;
int i = -1;
while ((i = in.read()) != -1) {
out.write(i);
totalBytesRead++;
int old = percentCompleted;
percentCompleted = (int)(((double)totalBytesRead / (double)connection.getContentLength()) * 100.0);
pcs.firePropertyChange("downloading", old, percentCompleted);
System.out.println(percentCompleted); // makes download a bit slower, comment out for speed
}
// Close streams
out.close();
in.close();
}
}
}
Controller.java:
package download_progress_bar;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
import java.awt.event.*;
import java.util.List;
import java.net.*;
import java.io.*;
import java.beans.*;
public class Controller {
private ProgressBar view;
private Downloader model;
private JProgressBar progressBar;
private SwingWorker<Void, Integer> worker;
public Controller(ProgressBar theView, Downloader theModel) {
view = theView;
model = theModel;
progressBar = view.getProgressBar();
// Add button listener to the "Start Download" button
view.addButtonListener(new ButtonListener());
}
class ButtonListener implements ActionListener {
/**
* Invoked when user clicks the button.
*/
public void actionPerformed(ActionEvent evt) {
view.turnOnButton(false);
progressBar.setIndeterminate(true);
// NOTE: Instances of javax.swing.SwingWorker are not reusable,
// so we create new instances as needed
worker = new Worker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("progress")) {
progressBar.setIndeterminate(false);
progressBar.setValue(worker.getProgress());
}
}
});
worker.execute();
}
}
class Worker extends SwingWorker<Void, Integer> implements PropertyChangeListener {
/*
* Download task. Executed in worker thread.
*/
#Override
protected Void doInBackground() throws MalformedURLException {
model.addListener(this);
try {
String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
model.setURL(src);
model.download();
} catch (IOException ex) {
System.out.println(ex);
this.cancel(true);
}
return null;
}
/*
* Executed in event dispatching thread
*/
#Override
protected void done() {
try {
if (!isCancelled()) {
get(); // throws an exception if doInBackground throws one
System.out.println("File has been downloaded successfully!");
}
} catch (InterruptedException x) {
x.printStackTrace();
System.out.println("There was an error in downloading the file.");
} catch (ExecutionException x) {
x.printStackTrace();
System.out.println("There was an error in downloading the file.");
}
view.turnOnButton(true);
}
/**
* Invoked in the background thread of Downloader.
*/
#Override
public void propertyChange(PropertyChangeEvent evt) {
this.setProgress((int) evt.getNewValue());
System.out.println("..." + this.getProgress() + "% completed");
}
}
}
Main.java:
package download_progress_bar;
import javax.swing.SwingUtilities;
/**
* Runs the download progress bar application.
*/
public class Main {
public static void main(String[] args) {
// Schedule a job for the event-dispatching thread:
// creating and showing this application's GUI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Create view
ProgressBar view = new ProgressBar();
// NOTE: Should model/controller be created outside invokeLater?
// Create model
Downloader model = new Downloader();
// Create controller
Controller controller = new Controller(view, model);
}
});
}
}
EDIT: I've updated my code to reflect the suggested changes. But even after making the changes, the problem persists. I am still seeing multiple invocations of "...100% completed", the number of invocations increasing with every subsequent download. For example, I run the application and press the download button for the first time, I will see
...100% completed
I press the download button again. I see
...100% completed
...100% completed
I press the download button again...
...100% completed
...100% completed
...100% completed
and so on. Why is this happening?
It's possible that, because of the way the percentage is calculated that it will report 100% when there is still some more work to be completed
During my testing I observed...
//...
98
...
99
99
...
100
So a lot of the values were repeated before the code completed.
I noted some issues/oddities in your download code, mostly the fact that you completely ignore the percentCompleted property, so I changed it to something more like...
public void download() throws IOException {
// Open connection on URL object
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Check response code (always do this first)
int responseCode = connection.getResponseCode();
System.out.println("response code: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
// Open input stream from connection
BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
// Open output stream for file writing
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("cat.jpg"));
int totalBytesRead = 0;
//int percentCompleted = 0;
int i = -1;
while ((i = in.read()) != -1) {
out.write(i);
totalBytesRead++;
int old = percentCompleted;
percentCompleted = (int) (((double) totalBytesRead / (double) connection.getContentLength()) * 100.0);
pcs.firePropertyChange("downloading", old, percentCompleted);
System.out.println(percentCompleted); // makes download a bit slower, comment out for speed
}
// Close streams
out.close();
in.close();
}
}
For me, I'd change the code slightly, instead of doing...
#Override
protected void process(List<Integer> chunks) {
int percentCompleted = chunks.get(chunks.size() - 1); // only interested in the last value reported each time
progressBar.setValue(percentCompleted);
if (percentCompleted > 0) {
progressBar.setIndeterminate(false);
progressBar.setString(null);
}
System.out.println("..." + percentCompleted + "% completed");
}
/**
* Invoked when a progress property of "downloading" is received.
*/
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("downloading")) {
publish((Integer) evt.getNewValue());
}
}
You should take advantage of SwingWorkers inbuilt progress support, for example...
/**
* Invoked when a progress property of "downloading" is received.
*/
#Override
public void propertyChange(PropertyChangeEvent evt) {
setProgress((int)evt.getNewValue());
}
This will mean you will need to attached a PropertyChangeListener to the SwingWorker
/**
* Invoked when user clicks the button.
*/
public void actionPerformed(ActionEvent evt) {
view.turnOnButton(false);
progressBar.setIndeterminate(true);
// NOTE: Instances of javax.swing.SwingWorker are not reusable,
// so we create new instances as needed
worker = new Worker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setIndeterminate(false);
progressBar.setValue(worker.getProgress());
}
}
});
worker.execute();
}
The side effect to this is, you know have a means to also be notified when the SwingWorker's state changes to check to see when it is DONE
Updated
Okay, after going over the code, again, I can see that you're adding a new PropertyChangeListener to model EVERY TIME you execute the SwingWorker
/*
* Download task. Executed in worker thread.
*/
#Override
protected Void doInBackground() throws MalformedURLException, InterruptedException {
model.addListener(this); // Add another listener...
try {
String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
model.setURL(src);
model.download();
} catch (IOException ex) {
System.out.println(ex);
this.cancel(true);
}
return null;
}
Because the model is an instance field of Controller, this is having an accumulative effect.
One solution might be to just add the Downloader as the listener to the model, but that would require you to ensure that any updates you perform to the UI are synced properly.
A better, general, solution would be to add support to remove the listener once the worker completes
public class Downloader {
//...
public void removeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
And then in the SwingWorkers done method, remove the listener...
/*
* Executed in event dispatching thread
*/
#Override
protected void done() {
model.removeListener(this);
As shown here and here, SwingWorker maintains two bound properties: state and progress. Invoking setProgress() ensures that "PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread." Simply add a PropertyChangeListener to your progress bar and call setProgress() in your implementation of doInBackground(), or a method that it calls such as download(). Conveniently, "For performance purposes, all these invocations are coalesced into one invocation with the last invocation argument only."

timer action performs only after the thread is finished

I have a swing GUI named SpyBiteDemo, it will call another class(Parser) and do some calculations and show some data in a jtable inside this GUI(SpyBiteDemo). I have a jbutton1 and I want to when click on it to show my timer to begin like 1,2,3,4,5,....seconds
what happens is my timer is running correctly however it does not show value unless it is done with all the program that is filling the jtable which means the action it is detecting is I perform the action after jtable appears.
I am a complete newbie on Java for event listeners and I have searched timer, timer task, schedule, everything and could not understand what's wrong.I also tried while(true) and did not fix it.I also tried duration of 1000,0,everything no affects.
I tried to use action command,sleeping the thread, it did not help.here is what I did:
public class SpyBiteDemo extends javax.swing.JFrame {
/**
* Creates new form SpyBiteDemo
*/
private long startTime;
Timer timer = new Timer(0, new TimerListener());
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent aEvt) {
long time = (System.currentTimeMillis() - startTime) / 1000;
label3.setText(time + " seconds");
}
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
jButton1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
timer.start();
}
});
startTime = evt.getWhen();
String SeedUrl = jTextField1.getText();
Parser P = new Parser(this);
jTable2.setVisible(true);
}
}
it will start showing label3 value only after it is filling the jtable on my jframe.I want the timer to start from when I am clicking the button.
with trashgod's links I had come up with this example which is comepletely runnable on your machine, this works perfect except that when I the program finishes, it does not stop the timer since I don't know where to do it, I know I should do it in addPropertyChangeListener, however I do not have timer value.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javaapplication7;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
/**
* #see http://stackoverflow.com/a/25526869/230513
*/
public class DisplayLog {
private static final String NAME = "C:\\wamp\\bin\\mysql\\mysql5.6.17\\bin\\scosche.sql";
private static class LogWorker extends SwingWorker<TableModel, String> {
private final File file;
private final DefaultTableModel model;
private LogWorker(File file, DefaultTableModel model) {
this.file = file;
this.model = model;
model.setColumnIdentifiers(new Object[]{file.getAbsolutePath()});
}
#Override
protected TableModel doInBackground() throws Exception {
BufferedReader br = new BufferedReader(new FileReader(file));
String s;
while ((s = br.readLine()) != null) {
publish(s);
}
return model;
}
#Override
protected void process(List<String> chunks) {
for (String s : chunks) {
model.addRow(new Object[]{s});
}
}
#Override
protected void done() {}
}
private void display() {
JFrame f = new JFrame("DisplayLog");
JLabel m=new JLabel("time");
JButton jb=new JButton("run");
f.add(jb,BorderLayout.BEFORE_FIRST_LINE);
f.add(m, BorderLayout.SOUTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DefaultTableModel model = new DefaultTableModel();
JTable table = new JTable(model);
JProgressBar jpb = new JProgressBar();
f.add(jpb, BorderLayout.NORTH);
f.add(new JScrollPane(table));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
LogWorker lw = new LogWorker(new File(NAME), model);
lw.addPropertyChangeListener((PropertyChangeEvent ev) -> {
SwingWorker.StateValue s = (SwingWorker.StateValue) ev.getNewValue();
jpb.setIndeterminate(s.equals(SwingWorker.StateValue.STARTED));
});
lw.execute();
int timeDelay = 0;
ActionListener time;
time = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
m.setText(System.currentTimeMillis() / 1000 + "seconds");
}
};
Timer timer=new Timer(timeDelay, time);
timer.start();
if(lw.isDone())
timer.stop();
}
});
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new DisplayLog().display();
});
}
}
It looks like the rows that comprise your table come from the network with variable, indeterminate latency. As shown here, you can load data into your TableModel in the background of a SwingWorker. As shown here, you can calculate intermediate progress in a way that makes sense for your application and display it in a PropertyChangeListener.

Swing Progress Bar updates via Worker to EventDispatch thread

I have a JAVA6 GUI handling data import to our database. I have implemented a working JProgressBar. I understand that changes made to the GUI must be done via the event dispatch thread--which I do not think I am doing (properly/at all).
the background Worker thread, UploadWorker, is constructed by passing in the a JProgressBar created in the main program, and sets changes the value of the progress bar directly once it is finished:
// when constructed, this gets set to the main program's JProgressBar.
JProgressBar progress;
protected Void doInBackground() throws Exception {
write("<!-- Import starting at " + getCurrentTime() + " -->\n");
boolean chunked = false;
switch (importMethod) {
//do some importing
}
write("<!-- Import attempt completed at " + getCurrentTime() + "-->\n");
//here changes to the GUI are made
progress.setMaximum(0);
progress.setIndeterminate(false);
progress.setString("Finished Working");
return null;
}
This works fine, but sometimes(not always) throws me several NPE's in the std out, and users are complaining:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.plaf.basic.BasicProgressBarUI.updateSizes(Unknown Source)
...etc...
Anyway, I believe there is something I need to do to get these updates executed on the proper thread, correct? How?
There are a number of ways you could do this, you could use the process method of the SwingWorker to also update the progress bar, but for me, this couples your worker to the UI, which isn't always desirable.
A better solution is to take advantage of the SwingWorkers progress and PropertyChange support, for example....
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE:
// Clean up here...
break;
}
} else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
// You could get the SwingWorker and use getProgress, but I'm lazy...
pb.setIndeterminate(false);
pb.setValue((Integer)evt.getNewValue());
}
}
});
worker.execute();
This means you could do this for ANY SwingWorker, so long as it was the worker was calling setProgress internally...
public static class ProgressWorker extends SwingWorker {
public static final int MAX = 1000;
#Override
protected Object doInBackground() throws Exception {
for (int index = 0; index < MAX; index++) {
Thread.sleep(250);
setProgress(Math.round((index / (float)MAX) * 100f));
}
return null;
}
}
The benefit of this is that the PropertyChange event notification is called from within the context of the of Event Dispatching Thread, making it safe to update the UI from within.
And fully runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SwingWorkerProgressExample {
public static void main(String[] args) {
new SwingWorkerProgressExample();
}
public SwingWorkerProgressExample() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pb;
public TestPane() {
setLayout(new GridBagLayout());
pb = new JProgressBar(0, 100);
pb.setIndeterminate(true);
add(pb);
ProgressWorker worker = new ProgressWorker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE:
// Clean up here...
break;
}
} else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
// You could get the SwingWorker and use getProgress, but I'm lazy...
System.out.println(EventQueue.isDispatchThread());
pb.setIndeterminate(false);
pb.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class ProgressWorker extends SwingWorker {
public static final int MAX = 1000;
#Override
protected Object doInBackground() throws Exception {
for (int index = 0; index < MAX; index++) {
Thread.sleep(250);
setProgress(Math.round((index / (float) MAX) * 100f));
}
return null;
}
}
}
You can just create a new Runnable that performs GUI updates and invoke it in a GUI thread using SwingUtilities.invokeLater

Categories