1

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.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • You may want to use [ProgressMonitorInputStream](https://docs.oracle.com/javase/8/docs/api/javax/swing/ProgressMonitorInputStream.html), which automatically opens a Progress Monitor during reading from a stream, if the process takes a non-trivial amount of time. – AJNeufeld Aug 09 '17 at 22:09

1 Answers1

1

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
        }
    }
}
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • Thank you sir. It will be more helpful if you can provide an example sooner. – ratan singh Aug 09 '17 at 23:14
  • Example added. Hope you can adapt it for your needs. Progress bar is just advancing by a fixed amount per timer tick, like you attempted to code in your problem sample code. Ideally, you could query the worker for the number of bytes copied, compute a real progress percentage, etc. – AJNeufeld Aug 10 '17 at 03:00
  • Thank you sir, i adapted the changes and it really helped me. – ratan singh Aug 10 '17 at 03:18