Search code examples
javaswingswingworkerpropertychangelistener

using property change listener to update swing components


I'm using the property change listener for the first time so I'm not quite familiar to the way it should be used between several classes.
I'm coding a download manager in java, in class Download there are fields like downloadSize and sizeOfFile and etc. there is also class DownloadPanel which is the GUI and is a jpanel and contains a JProgressbar and several JLabels to show amount of file which is downloaded or size of file (using Download fields).
the Download class extends SwingWorker and downloads a given file from a specific URL using HttpURLConnection.
while downloading a file in order to update its download panel, I have implemented property change listener. the problem is that JProgressbar is being updated correctly but JLabel showing downloadedSize and sizeOfFile doesn't change through a downaloding a file.

note that unrelated parts and getters/setters in classes are omitted and just parts which are related to the question are included.

implementation of Property Change Listener:

public class DownloadPanelPropertyListener implements PropertyChangeListener {

    Download download;

    public DownloadPanelPropertyListener(Download download) {
        this.download = download;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("progress".equals(evt.getPropertyName()))
            download.getDownloadPanel().getJpb().setValue((Integer) evt.getNewValue());
        else if ("downloadPanel".equals(evt.getPropertyName())) {
            DownloadPanel temp = (DownloadPanel) evt.getNewValue();
            int ds = temp.download.getDownloadedSize();
            int sof = temp.download.getSizeOfFile();
            download.getDownloadPanel().setDownloadedSizeLabel(ds, sof);
        }
    } 

Code of DownloadPanel class:

public class DownloadPanel extends JPanel {

    Download download;
    JProgressBar jpb = new JProgressBar(0,100);
    JLabel downloadSpeedLabel;
    JLabel downloadedSizeLabel;

    public DownloadPanel (Download d) {
        download = d;
        this.addPropertyChangeListener("downloadPanel",new DownloadPanelPropertyListener(download));
        jpb.setValue( (int) (( (double) download.getDownloadedSize() / (double) download.getSizeOfFile()) * 100)) ;
        jpb.setBounds(100,25,400,10);
        jpb.setIndeterminate(false);
        JLabel progressBarValue = new JLabel("%" + jpb.getValue() + "");
        progressBarValue.setBounds(510,18,25,20);
        add(progressBarValue);
        downloadSpeedLabel = new JLabel (String.format ("%dKbs",download.getDownloadSpeed()));
        downloadedSizeLabel = new JLabel (String.format ("%d MG / %d MG",download.getDownloadedSize(),download.getSizeOfFile()));
        add(downloadSpeedLabel); add(downloadedSizeLabel);
    }  

and class Download:

public class Download extends SwingWorker<Void,Void> implements Serializable,Runnable {

    private transient DownloadPanel downloadPanel = null;
    private transient MainPage mp;
    private String fileName;
    private String hostName;
    private int downloadSpeed;
    private int downloadedSize;
    private int sizeOfFile;
    private int queueIndex;
    private URL url;

    public Download (MainPage c,URL url,File f,int index) {

        addPropertyChangeListener(new DownloadPanelPropertyListener(this));
        mp = c;
        this.url = url;
        queueIndex = index;
        downloadTime = time;
        fileName = url.getFile();
        hostName = url.getHost();
        sizeOfFile = -1;
        downloadSpeed = 0;
        downloadedSize = 0;
    }

    public Void doInBackground () {
        RandomAccessFile file = null;
        InputStream stream = null;

        try {
            // Open connection to URL.
            HttpURLConnection connection =
                    (HttpURLConnection) url.openConnection();

            // Specify what portion of file to download.
            connection.setRequestProperty("Range",
                    "bytes=" + downloadedSize + "-");

            // Connect to server.
            connection.connect();

            // Make sure response code is in the 200 range.
            if (connection.getResponseCode() / 100 != 2) {
                System.out.println("0");
            }

            // Check for valid content length.
            int contentLength = connection.getContentLength();
            if (contentLength < 1) {
                System.out.println("1");
            }

      /* Set the size for this download if it
         hasn't been already set. */
            if (sizeOfFile == -1) {
                sizeOfFile = contentLength;
            }

            // Open file and seek to the end of it.
            file = new RandomAccessFile(new File(s.getCurrentDirectory(),getFileName(url)),
                    "rw");
            file.seek(downloadedSize);

            stream = connection.getInputStream();
            while (status == CURRENT) {
        /* Size buffer according to how much of the
           file is left to download. */
                byte buffer[];
                if (sizeOfFile - downloadedSize > MAX_BUFFER_SIZE) {
                    buffer = new byte[MAX_BUFFER_SIZE];
                } else {
                    buffer = new byte[sizeOfFile - downloadedSize];
                }

                // Read from server into buffer.
                int read = stream.read(buffer);
                if (read == -1)
                    break;

                // Write buffer to file.
                file.write(buffer, 0, read);
                downloadedSize += read;
                setProgress ( (int) (( (double) getDownloadedSize() / (double) getSizeOfFile()) * 100));
            }

      /* Change status to complete if this point was
         reached because downloading has finished. */
            if (status == CURRENT) {
                status = COMPLETE;
            }
        } catch (Exception e) {
            System.out.println("2");
            e.printStackTrace();
        } finally {
            // Close file.
            if (file != null) {
                try {
                    file.close();
                } catch (Exception e) {}
            }

            // Close connection to server.
            if (stream != null) {
                try {
                    stream.close();
                } catch (Exception e) {}
            }
        }
        return null;
    }

    private String getFileName(URL url) {
        String fileName = url.getFile();
        return fileName.substring(fileName.lastIndexOf('/') + 1);
    }

Solution

  • I don't see you ever setting the JLabel's text within your property change listener, but having said that, you shouldn't do this as it violates OOP encapsulation. Rather:

    • Give your DownloadPanel class a public void setPercentDownload(int value) method, and in this method, set the JProgressBar and the JLabel's text via setText(...)
    • In the Prop change listener, call this method passing in the value to the GUI.
    • Don't set the progress bar's value in the listener, let the view do this as I've noted above.
    • Be sure to listen for newValue == SwingWorker.StateValue.DONE in your prop change listener. When this occurs, call get() on your worker so you can trap any exceptions that might have occurred within the background thread.
    • Side issue: just say "no" to null layouts and setBounds. They make for rigid GUI's that don't work on all platforms and are a bear to debug and enhance. Use the layout managers.

    For example, my Minimal, Complete, and Verifiable example (MCVE):

    import java.awt.BorderLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import java.beans.*;
    import java.io.Serializable;
    import java.util.concurrent.ExecutionException;
    
    import javax.swing.*;
    
    public class FooProgress  {
        private static void createAndShowGui() {
            JFrame frame = new JFrame("FooProgress");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(new DownloadPanel());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }
    

    class DownloadListener implements PropertyChangeListener {
        private DownloadPanel downloadPanel;
    
        public DownloadListener(DownloadPanel downloadPanel) {
            this.downloadPanel = downloadPanel;
        }
    
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("progress".equals(evt.getPropertyName())) {
                int value = (int) evt.getNewValue();
                downloadPanel.setPercentDownload(value);
            } else if ("state".equals(evt.getPropertyName())) {
                if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                    Download download = (Download) evt.getSource();
                    try {
                        download.get();
                    } catch (InterruptedException | ExecutionException e) {
                        // TODO: handle exceptions here
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    @SuppressWarnings("serial")
    class DownloadPanel extends JPanel {
        private static final String PROGRESS_FORMAT = "%03d%%";
        private JProgressBar jpb = new JProgressBar(0, 100);
        private JLabel downloadedSizeLabel = new JLabel(String.format(PROGRESS_FORMAT, 0));
        private DownLoadAction downLoadAction = new DownLoadAction();
    
        public DownloadPanel() {
            JPanel topPanel = new JPanel();
            topPanel.add(new JLabel("Download Progress:"));
            topPanel.add(downloadedSizeLabel);
            JPanel bottomPanel = new JPanel();
            bottomPanel.add(new JButton(downLoadAction));
    
            setLayout(new BorderLayout());        
            add(topPanel, BorderLayout.PAGE_START);
            add(jpb);
            add(bottomPanel, BorderLayout.PAGE_END);
        }
    
        public void setPercentDownload(int value) {
            downloadedSizeLabel.setText(String.format(PROGRESS_FORMAT, value));
            jpb.setValue(value);
            if (value == 100) {
                downLoadAction.setEnabled(true);
            }
        }
    
        private class DownLoadAction extends AbstractAction {
            public DownLoadAction() {
                super("Download");
                putValue(MNEMONIC_KEY, KeyEvent.VK_D);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                setEnabled(false);
                setPercentDownload(0);
                Download download = new Download();
                DownloadListener listener = new DownloadListener(DownloadPanel.this);
                download.addPropertyChangeListener(listener);
                download.execute();
            }
        }
    }
    

    class Download extends SwingWorker<Void, Void> implements Serializable, Runnable {
    
        private static final long SLEEP_TIME = 200;
    
        public Void doInBackground() throws Exception {
            int myProgress = 0;
            while (myProgress < 100) {
                myProgress += (int) (10 * Math.random());
                myProgress = Math.min(myProgress, 100);
                setProgress(myProgress);
                Thread.sleep(SLEEP_TIME);
            }
            return null;
        }
    
    }
    

    In the future, you'll want to create and post MCVE's with your code so we can actually compile and run and test it without difficulty. Your code has too much file handling in it that we cannot test presently. I've substituted Thread.sleep for 90% of that code.