I have a customized JFileChooser
Its approveSelection() method is slightly modified:
public void approveSelection()
{
File file = getSelectedFile();
changeGui();
final Object a = makeALongCalcualtion(file);
if (a != null)
{
super.approveSelection();
SwingWorker<Document, Void> open = new SwingWorker<Document, Void>()
{
#Override
protected Document doInBackground() throws Exception
{
return createADocument(a);
}
#Override
protected void done()
{
try
{
if(get() != null)
{
changeGui();
}
else
{
//TODO error message
changeGui();
}
}
catch (InterruptedException | ExecutionException e)
{
//TODO error message
changeGui();
}
}
};
open.execute();
}
else
{
//TODO error message
changeGui();
}
}
The changeGui() method sets a JProgressBar to indeterminate and updates a JLabel with a new string.
If file provided to makeALongCalcualtion(file) is of invalid type, it will return null, otherwise it returns info that is passed to SwingWorker which can use it to acutally create the representation of a file in the program (the Document object).
However, this doesn't work as it should because makeALongCalcualtion(file) isn't called within SwingWorker method, and that blocks the EDT.
In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it.
So the bottom line is, how do I call super.approveSelection() from within doInBackground() for this specific case?
//More info
What is supposed to happen:
User selects and opens a file
JLabel and JProgressBar are updated, indeterminate progress starts to play.
If makeALongCalcualtion(file) return null user is warned with an error window, but the JFileChooser stys open, making it possible to choose again when the error window is closed.
Otherwise, super.approveSelection() is called, allowing the chooser to close.
A document is created (but the method that creates the document return null if something goes wrong).
If everything is fine, JLabel updates and progressBar animation is stopped (indeterminate is set to false).
If something goes wrong same thing happens as in step 6, only with different message in JLabel.
What happens:
same
same
same, but when makeALongCalculation(file); begins, progressBar freezes.
same
same
same, but the animation isn't stopped (since the progressbar is frozen), only the frozen "picture" is removed and progressbar returns to it's previous state.
same
EDIT
I have made some alterations to my program and I now have this:
approveSelection():
public void approveSelection()
{
File file = getSelectedFile();
Main.getStatusBar().startOpen();
final WorkOpen open = new WorkOpen(file);
open.execute();
open.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName())) {
if (evt.getNewValue().equals("DONE"))
{
if (open.failed())
{
//TODO error message
Main.getStatusBar().endOpen(false);
}
else
{
Main.getStatusBar().endOpen(true);
}
}
}
}
});
}
SwingWorker:
class WorkOpen extends SwingWorker<Document, Void>
{
boolean failed = false;
File file;
public boolean failed()
{
return failed;
}
#Override
protected Document doInBackground() throws Exception
{
ArrayList<String> data = Opener.extractData(file);
if (data != null)
{
//My little path/name/similar managing system
FileComplex fullPath = new FileComplex(file.toString());
return Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data);
}
else
{
failed = true;
return null;
}
}
#Override
protected void done()
{
try
{
if(get() != null)
{
Main.addDocument(get());
}
}
catch (InterruptedException | ExecutionException e)
{
failed = true;
}
}
WorkOpen(File file)
{
this.file = file;
}
}
The problem now is where to call super.approveSelection(). It has to wait for the worker to finish executing, yet I can't call it from the property change listener.
What to do here?
EDIT 2
As HovercraftFullOfEels suggested, I fixed my code and it compiled and ran. But the problem of JProgressBar freezeing remained. Also, I had to introduce something I don't know if I should have:
private void superApproveSelection()
{
super.approveSelection();
}
public void approveSelection()
{
final File file = getSelectedFile();
class OpenWorker extends SwingWorker<Boolean, Void>
{
Document document;
Document getDocument()
{
return document;
}
#Override
protected Boolean doInBackground() throws Exception
{
ArrayList<String> data = Opener.extractData(file);
if (data != null)
{
//I had to start the progressBar here, because if invalid
//file was selected (extractData(file) returns null if it was),
//nothing should happen (maybe an error
//window later, handled with a new Runnable() same as this:
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
Main.getStatusBar().startOpen();
}
});
FileComplex fullPath = new FileComplex(file.toString());
document = Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data);
return true;
}
else
{
return false;
}
}
};
final OpenWorker opener = new OpenWorker();
opener.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if ("state".equals(evt.getPropertyName()))
{
if (evt.getNewValue() == SwingWorker.StateValue.DONE)
{
if(opener.getDocument() != null)
{
superApproveSelection();
Main.addDocument(opener.getDocument());
Main.getStatusBar().endOpen(true);
}
else
{
try
{
//I'm retrieveing doInBackground()'s value to see if
//progressBar needs stoping (it also displays some
//text, so it must not be called unless the
//progressBar was started).
if (opener.get())
{
Main.getStatusBar().endOpen(false);
}
}
catch (InterruptedException | ExecutionException e)
{
//TODO error something went wrong
}
}
}
}
}
});
opener.execute();
}
"In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it."
No, not true at all. super.approveSelection() would not have to be called inside of the SwingWorker.
Why not simply create a SwingWorker, add a PropertyChangeListener to it, and when the SwingWorker's state is done, call super.approveSelection() if indicated?
OK, here is my example below. Explanation to follow:
import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
import javax.swing.text.*;
#SuppressWarnings("serial")
public class ApproveSelectionTest extends JPanel {
private JTextArea textArea = new JTextArea(30, 60);
public ApproveSelectionTest() {
textArea.setEditable(false);
textArea.setFocusable(false);
JPanel btnPanel = new JPanel();
btnPanel.add(new JButton(new MyGetFileAction("Get Text File Text")));
setLayout(new BorderLayout());
add(new JScrollPane(textArea), BorderLayout.CENTER);
add(btnPanel, BorderLayout.PAGE_END);
}
private class MyGetFileAction extends AbstractAction {
public MyGetFileAction(String text) {
super(text);
}
public void actionPerformed(java.awt.event.ActionEvent arg0) {
MyFileChooser myFileChooser = new MyFileChooser();
int result = myFileChooser.showOpenDialog(ApproveSelectionTest.this);
if (result == JFileChooser.APPROVE_OPTION) {
Document doc = myFileChooser.getDocument();
textArea.setDocument(doc);
}
};
}
private static void createAndShowGui() {
ApproveSelectionTest mainPanel = new ApproveSelectionTest();
JFrame frame = new JFrame("ApproveSelectionTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class MyFileChooser extends JFileChooser {
private WorkOpen workOpen = null;
private JDialog progressDialog = null;
public MyFileChooser() {
}
#Override
public void approveSelection() {
JProgressBar progBar = new JProgressBar();
progBar.setIndeterminate(true);
Window win = SwingUtilities.getWindowAncestor(this);
progressDialog = new JDialog(win, "Checking File", ModalityType.APPLICATION_MODAL);
progressDialog.getContentPane().add(progBar);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
File file = getSelectedFile();
workOpen = new WorkOpen(file);
workOpen.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (SwingWorker.StateValue.DONE == pcEvt.getNewValue()) {
if (progressDialog != null) {
progressDialog.dispose();
}
try {
boolean bool = workOpen.get().booleanValue();
if (bool) {
superApproveSelection();
} else {
JOptionPane.showMessageDialog(MyFileChooser.this, "Invalid File Chosen");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
});
workOpen.execute();
progressDialog.setVisible(true);
}
// ****** this is key *****
private void superApproveSelection() {
super.approveSelection();
}
public Document getDocument() {
if (workOpen == null) {
return null;
} else {
return workOpen.getDocument();
}
}
}
class WorkOpen extends SwingWorker<Boolean, Void> {
private static final long SLEEP_TIME = 4 * 1000;
private Document document = null;
private File file = null;
public WorkOpen(File file) {
this.file = file;
}
#Override
protected Boolean doInBackground() throws Exception {
if (file == null || !file.exists()) {
return Boolean.FALSE;
}
Thread.sleep(SLEEP_TIME);
String fileName = file.getName();
if (fileName.contains(".txt")) {
Scanner scan = new Scanner(file);
StringBuilder stringBuilder = new StringBuilder();
while (scan.hasNextLine()) {
stringBuilder.append(scan.nextLine() + "\n");
}
document = new PlainDocument();
document.insertString(0, stringBuilder.toString(), null);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Document getDocument() {
return document;
}
}
Explanation and key points from my example:
This example behaves very simply. You choose a file, and if the file exists and contains ".txt" in its name, then it reads in the document and displays the text in a JTextField.
Else it displays a warning message but leaves the JFileChooser displayed.
Probably the key point: I've given my MyFileChooser class a private superApproveSelection() method that can be called by my PropertyChangeListener. This exposes the super's approveSelection() method to the inner class, one of the problems you were having.
The order of calling code is important in my approveSelection() override.
In this method I first create my JProgressBar and its dialog but don't yet display it immediately. It really doesn't have to be created first, but it sure needs to be displayed last.
I create my SwingWorker, workOpen, but don't yet execute it.
I add my PropertyChangeListener to the SwingWorker before executing the SwingWorker.
I then execute my SwingWorker
I then display my modal JDialog with the indeterminate JProgressBar.
My SwingWorker is structured so that its doInBackground returns a Boolean, not a Document.
I have it create a (very simple) Document if all works out OK that holds the content of the text file, and set a private "doc" field obtainable by a getter method, and then have doInBackground return Boolean.TRUE if all works well.
I've given my doInBackground a Thread.sleep(4000) just to pretend that its action takes a lot of time. Yours of course won't have this.
In the PropertyChangeListener if the SwingWorker is DONE, I'll dispose of the progress bar dialog and then call get() on the SW to get the Boolean result.
If it's Boolean.TRUE, then call the superApproveSelection() method described above.
Else show an error message. Note that since the super's approveSelection() isn't called, the file chooser dialog remains displayed.
If the approveSelection is called then the code that displays my file chooser dialog will get the appropriate return value, will extract the Document from the file chooser and displays the Document in a JTextArea.
Related
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
}
}
}
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."
I am trying to show a progress bar while I do some tasks on a database. The Progress bar, however, freezes and the Things I want to do on the database aren't executed. I understand that, in order to guarantee proper concurrency in Swing I need to do the database tasks on a secondary thread. I also understand that somehow my bug has to do with JOptionPane. But I can't come up with a solution to fix it. Here is the Code for my Progress Dialog:
public class ProgressDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = 1L;
public ProgressDialog() {
setModal(true);
setTitle("Fortschritt");
setSize(200, 100);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
JProgressBar pb = new JProgressBar();
pb.setIndeterminate(true);
pb.setValue(0);
add(pb);
setVisible(true);
}
}
And here is the Code where I call this constructor:
int result = JOptionPane.showConfirmDialog(GUIAutoTest.jtable,
"Schaden mit Testkonfig = " + index + " anlegen ?", "Bestätigen",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
new SwingWorker<Void, Void>() {
final ProgressDialog pd = new ProgressDialog();
#Override
protected Void doInBackground() throws Exception {
InitTestLauf itl;
try {
itl = new InitTestLauf(index);
StartTestLauf stl = new StartTestLauf(itl.getIdTstLauf());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void done() {
System.out.println("done");
pd.setVisible(false);
}
}.execute();
JOptionPane.showMessageDialog(GUIAutoTest.jtable,
"Schaden angelegt. " + "Schadennummer: " + StartTestLauf.getSchadenNr(),
"Schaden angelegt", JOptionPane.PLAIN_MESSAGE);
It doesn't matter, what happens inside the doInBackground()-block , not even System.out.println("print something") does work. Where is my mistake ?
Thanks in advance!
I made an example that uses a progress bar with a dialog and a swingworker.
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import java.util.List;
/**
* Created on 13.06.17.
*/
public class DialogJunker {
static class ProgressDialog extends JDialog {
JProgressBar bar;
ProgressDialog(){
setModal(true);
bar = new JProgressBar();
add(bar);
pack();
}
void setProgress(int i){
bar.setValue(i);
}
}
public static void main(String[] args){
JFrame frame = new JFrame("diddly dialog");
JButton button = new JButton("start");
button.addActionListener(evt->{
ProgressDialog log = new ProgressDialog();
new SwingWorker<Void, Integer>(){
#Override
public Void doInBackground(){
for(int i = 0; i<100; i++){
try{
Thread.sleep(10);
publish(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
public void done(){
log.setVisible(false);
log.dispose();
}
#Override
protected void process(List<Integer> ints){
log.setProgress(ints.get(0));
}
}.execute();
log.setVisible(true);
});
frame.add(button);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
This example will show a dialog with a progress bar that gets updated, then close the dialog when finished.
After reviewing you code a little more, I do see the problem. You are constructing the ProgressDialog in the SwingWorker class, but you set your progress dialog to visible, which blocks. Take note that I have solved quite a few issues.
I call set visible after starting the swing worker.
I publish the results so that the dialog actually gets updated.
I keep a reference to the progress bar, so it actually can be updated.
I am trying to get my application to display a simple loading dialog so users know when a time intensive process is working and when its done. I just want it to show a simple "loading" using a gif I downloaded. I already tried using only text and it still doesn't work.
I can get the dialog to display (and disappear) when I want it to, the problem is nothing will display on the dialog (or frame) after displaying it. I have tried many different techniques and all give the same result, a blank dialog.
I finally made a separate class to display the dialog (with loading gif) and I got it to display properly (by itself), but when I run it from my main application, it shows a black dialog again. I tested putting the gif into a JOptionPane and it works, the problem with that is I can't close it at will.
Here is my custom code.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import java.util.logging.*;
import org.w3c.dom.*;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import javax.swing.filechooser.FileNameExtensionFilter;
public class Loader implements Runnable {
final JFileChooser jfc = new JFileChooser();
static JFrame frame = new JFrame();
Frame parentUI = new Frame();
JDialog dialog = new JDialog();
JLabel lbl_filename = new JLabel();
JLabel lbl_path = new JLabel();
static Loader load = new Loader(null);
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
load.run();
frame.setVisible(true);
}
public Loader(Frame parent) {
init();
parentUI = parent;
}
#Override
public void run() {
createDialog(parentUI);
}
public final void init() {
JButton btn = new JButton("Open");
frame.setTitle("Loader Test");
frame.setSize(500, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setLayout(new FlowLayout());
btn.addActionListener(new Action1());
frame.add(btn);
frame.add(lbl_filename);
frame.add(lbl_path);
}
class Action1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
openFile();
load.Close();
}
}
private void createDialog(final Frame parent) {
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setTitle("Loader");
URL url = this.getClass().getResource("/resource/loader.gif");
Icon icon = new ImageIcon(url);
JLabel label = new JLabel(icon);
dialog.add(label);
dialog.pack();
dialog.setLocationRelativeTo(parent);
}
public void Show(Boolean visible) {
this.run();
dialog.setVisible(visible);
}
public void Close() {
dialog.setVisible(false);
}
private void setJFCFilter(String file, String ext) {
FileNameExtensionFilter filter = new FileNameExtensionFilter(file, ext);
jfc.setFileFilter(filter);
}
private void openFile() {
File default_dir = new File(".");
jfc.setCurrentDirectory(default_dir);
setJFCFilter("Scalable Vector Graphics", "svg");
int returnVal = jfc.showOpenDialog(parentUI);
if (returnVal == JFileChooser.APPROVE_OPTION) {
String path = jfc.getSelectedFile().getAbsolutePath();
String fileName = jfc.getSelectedFile().getName();
lbl_filename.setText(fileName);
lbl_path.setText(path);
load.Show(true);
createDoc(path);
load.Close();
}
}
private void createDoc(String file) {
try {
NodeList svgIDPaths;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file);
String xpathIDExp = "//g/#id";
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
XPathExpression expression = xpath.compile(xpathIDExp);
svgIDPaths = (NodeList)expression.evaluate(doc, XPathConstants.NODESET);
} catch (Exception ex) {
Logger.getLogger(Loader.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Edit: Use this file for testing -> svg_test.svg
I have tried calling it like this:
loader.show(true);
And also in its own thread like this:
private void load(final Boolean visible) {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
loader.show(visible);
}
});
t.start();
}
Neither method works and gives me the same result, a blank dialog. I have had this issue in the past, but just gave up and removed it (loading dialog). I have tried it with a progress bar and simple text, nothing seems to work.
Also I tried it in a JOptionPane and it worked, but that's not desirable (I want to close/open when I want not via a button click).
private void load() {
ImageIcon icon = new ImageIcon(MainForm.class.getResource("/resource/loader.gif").getFile());
JOptionPane.showMessageDialog(null, "Loading...", "Loader", JOptionPane.INFORMATION_MESSAGE, icon);
}
I am aware you can't run multiple dialogs on the EDT and have to use a separate thread, but I'm using a separate thread and its not working (it works by itself).
(Also note I have one main application (frame) that is running/opening this second dialog).
Any assistance is appreciated.
You look to have a Swing threading issue where you have long-running code on the event thread messing up drawing of images, and my guess is that the long running code is in your createDoc method. Consider calling that from a background thread, such as from a SwingWorker, and calling close on your load object only after the worker has completed its work. For example something like so:
class Action1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
openFile();
// load.Close(); // get rid of this
}
}
// .......
private void openFile() {
// ....
load.Show(true); // load dialog on event thread
new SwingWorker<Void, Void>() {
protected Void doInBackground() throws Exception {
createDoc(path); // call this from background thread
return null;
};
protected void done() {
load.Close(); // only call this once createDoc has completed
// probably should call get() in here to catch all exceptions
};
}.execute();
}
Below is the compiled program replica of actual problem code,
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Dummy {
public static boolean getUserCheck(int size, boolean Check) {
if (Check) {
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
if (ret > 0) {
System.out.println("User said No: " + ret);
return false;
} else if (ret <= 0) {
System.out.println("user said Yes: " + ret);
return true;
}
}
return true;
}
public static void workerMethod1() {
System.out.println("am worker method 1");
}
public static void workerMethod2() {
System.out.println("am worker method 2");
}
public static void main(String[] args) {
System.out.println("mainthread code line 1");
int size = 13;
boolean thresholdBreach = true;
if (getUserCheck(size, thresholdBreach)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod1();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod2();
}
});
}
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
}
}
where i would like to run the if{} block in main() on separate thread. Because these 2 lines,
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
need not wait for completion of if(){} block
Another problem is, experts recommend to run confirm-dialog methods on event thread.
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
Please help me!!!!
JOptionPane is a Swing method and should be called on the EDT, the Event Dispatch Thread, and only on this thread, and so it suggests that all your code above should be on the EDT, and that most of your SwingUtilities.invokeLater(new Runnable() calls are completely unnecessary. The only necessary ones will be the main one, where you launch your Swing GUI code, and any areas where Swing calls need to be made from within background threads. Again, if any of the above code is being made within background threads, then the JOptionPane should not be in that thread.
For more specific information in this or any other answer, please provide more specific information in your question. Let's end all confusion. The best way to get us to fully and quickly understand your problem would be if you were to to create and post a minimal example program, a small but complete program that only has necessary code to demonstrate your problem, that we can copy, paste, compile and run without modification.
I have a sneaking suspicion that a decent refactoring along MVC lines could solve most of your problems. Your code is very linear with its lines of code that must follow one another and its if blocks, and it is also tightly coupled with your GUI, two red flags for me. Perhaps better would be less linear code, more event and state-driven code, code where your background code interacts with the GUI via observer notification, and where the background code likewise responds to state changes in the GUI from control notification.
Your control needs two SwingWorkers, one to get the row count and the other to get the rest of the data if the user decides to do so. I'd add a PropertyChangeListener to the first SwingWorker to be notified when the row count data is ready, and then once it is, present it to the view for the user to select whether or not to proceed. If he decides to proceed, I'd then call the 2nd SwingWorker to get the main body of the data.
For example, a rough sketch of what I'm talking about:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerFooView extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 300;
private JProgressBar progressBar;
private JDialog dialog;
public SwingWorkerFooView() {
add(new JButton(new ButtonAction("Foo", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public boolean showOptionGetAllData(int numberOfRows) {
String message = "Number of rows = " + numberOfRows + ". Get all of the data?";
String title = "Get All Of Data?";
int optionType = JOptionPane.YES_NO_OPTION;
int result = JOptionPane.showConfirmDialog(this, message, title, optionType);
return result == JOptionPane.YES_OPTION;
}
public void showProgressBarDialog() {
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
Window window = SwingUtilities.getWindowAncestor(this);
dialog = new JDialog(window, "Hang on", ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel();
panel.add(progressBar);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
public void closeProgressBarDialog() {
dialog.dispose();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorkerFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerFooView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ButtonAction extends AbstractAction {
Workers workers = new Workers();
private SwingWorker<Integer, Void> firstWorker;
private SwingWorker<List<String>, Void> secondWorker;
private SwingWorkerFooView mainGui;
public ButtonAction(String name, SwingWorkerFooView mainGui) {
super(name);
this.mainGui = mainGui;
}
#Override
public void actionPerformed(ActionEvent e) {
firstWorker = workers.createFirstWorker();
firstWorker.addPropertyChangeListener(new FirstPropertyChangeListener());
firstWorker.execute();
mainGui.showProgressBarDialog();
}
private class FirstPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
int numberOfRows = firstWorker.get();
boolean getAllData = mainGui.showOptionGetAllData(numberOfRows);
if (getAllData) {
secondWorker = workers.createSecondWorker();
secondWorker.addPropertyChangeListener(new SecondPropertyChangeListener());
secondWorker.execute();
mainGui.showProgressBarDialog();
} else {
// user decided not to get all data
workers.cleanUp();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
private class SecondPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
List<String> finalData = secondWorker.get();
// display finalData in the GUI
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
class Workers {
// database object that may be shared by two SwingWorkers
private Object someDataBaseVariable;
private Random random = new Random(); // just for simulation purposes
private class FirstWorker extends SwingWorker<Integer, Void> {
#Override
protected Integer doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
// here we create our database object and check how many rows there are
int rows = random.nextInt(10 + 10); // this is just for demonstration purposes only
// here we create any objects that must be shared by both SwingWorkers
// and they will be saved in a field of Workers
someDataBaseVariable = "Fubar";
return rows;
}
}
private class SecondWorker extends SwingWorker<List<String>, Void> {
#Override
protected List<String> doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
List<String> myList = new ArrayList<>();
// here we go through the database filling the myList collection
return myList;
}
}
public SwingWorker<Integer, Void> createFirstWorker() {
return new FirstWorker();
}
public void cleanUp() {
// TODO clean up any resources and database stuff that will not be used.
}
public SwingWorker<List<String>, Void> createSecondWorker() {
return new SecondWorker();
}
}
The key to all of this is to not to think in a linear console program way but rather to use observer design pattern, i.e., listeners of some sort to check for change of state of both the GUI and the model.
It's essentially:
create worker
add observer to worker (property change listener)
execute worker
show progress bar dialog or notify user in some way that worker is executing.
The listener will be notified when the worker is done, and then you can query the worker (here via the get() method call) as to its end result.
Then the progress dialog can be closed
And the view can display the result or get additional information from the user.
Yes; SwingUtilities.invokeLater() simply places your runnable on the AWT event queue to be processed later, and it is safe to do so at any time.