Search code examples
javaswingprogressjprogressbar

JProgressBar show status of another class void


I have a class that performs copying from one folder list of files which is loaded first as .txt , then is copying to temporary folder. I want to show progress of copying files to folder and vice-versa

This is my copy class:

public class CopyService {

private String _destLocation;
private List<File> _filePaths = new ArrayList<File>();
private Map<String, String> _fileProperties = new HashMap<String, String>();
private String _rootLocation;

private File _txtFile;

public CopyService() {
setRootLocation(NekretnineService.getRootFolder());
setDestLocation(NekretnineService.getDestFolder());
}

public void copyImagesToTempFolder() throws IOException {
File destLocationFolder = new File(getDestLocation());
if (getFilePaths() != null) {
    if (!destLocationFolder.exists()) {
    destLocationFolder.mkdir();
    }
    for (File f : getFilePaths()) {
    if (f.exists()) {
        FileUtils.copyFileToDirectory(f, destLocationFolder);
    }
    }
}
}

public String getDestLocation() {
return _destLocation;
}

public List<File> getFilePaths() {
return _filePaths;
}

public Map<String, String> getFileProperties() {
return _fileProperties;
}

public String getRootLocation() {
return _rootLocation;
}

public File getTxtFile() {
return _txtFile;
}

public void loadTxtFile(File txtFile) throws Exception {
try {
    int lines = 0;
    List<String> imgNames = new ArrayList<String>();
    try (BufferedReader br = new BufferedReader(new FileReader(txtFile))) {
    String txtLine;
    while ((txtLine = br.readLine()) != null) {
        imgNames.add(txtLine);
        lines++;
    }
    }
    for (String img : imgNames) {
    String fullPath = getRootLocation();
    fullPath += File.separator + img;
    getFilePaths().add(new File(fullPath));
    fullPath = "";
    }
    _fileProperties.put("filename", txtFile.getName());
    _fileProperties.put("filesize", String.valueOf(txtFile.length()));
    _fileProperties.put("totalimgs", String.valueOf(lines));
} catch (Exception e) {
    System.out.println(e.getMessage());
}
}

public void setDestLocation(String destLocation) {
_destLocation = destLocation;
}

public void setFilePaths(List<File> filePaths) {
_filePaths = filePaths;
}

public void setFileProperties(Map<String, String> fileProperties) {
_fileProperties = fileProperties;
}

public void setRootLocation(String rootLocation) {
_rootLocation = rootLocation;
}

public void setTxtFile(File txtFile) {
_txtFile = txtFile;
}

@Override
public String toString() {
return "CopyService [_destLocation=" + _destLocation + ", _filePaths=" + _filePaths + ", _fileProperties="
    + _fileProperties + ", _rootLocation=" + _rootLocation + ", _txtFile=" + _txtFile + "]";
}

}

And this is my GUI window made with window builder:

public class MainWindow extends JFrame {

private static final long serialVersionUID = 1L;
private JPanel _contentPane;
private CopyService copyService;
private JLabel lblFileName;
private JLabel lblFileSize;
private JLabel lblImagesCount;
private JTextField txtImgDestination;
private JTextField txtRootLocation;

public MainWindow() {
Image img = new ImageIcon(getClass().getClassLoader().getResource("ico_house.png")).getImage();
setIconImage(img);
setSize(450, 341);
setTitle("Nekretnine Service");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
setLocationRelativeTo(null);
_contentPane = new JPanel();
_contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(_contentPane);
_contentPane.setLayout(null);

JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);
tabbedPane.setBounds(0, 0, 434, 261);
_contentPane.add(tabbedPane);

JPanel backupTab = new JPanel();
tabbedPane.addTab("Backup", null, backupTab, null);

JPanel imgCleanerTab = new JPanel();
tabbedPane.addTab("Image Cleaner", null, imgCleanerTab, null);
imgCleanerTab.setLayout(null);

JPanel panel = new JPanel();
panel.setBorder(new BevelBorder(BevelBorder.LOWERED, Color.BLACK, null, null, null));
panel.setBounds(10, 11, 409, 73);
imgCleanerTab.add(panel);
panel.setLayout(null);

lblFileName = new JLabel("Filename:");
lblFileName.setBounds(20, 11, 247, 14);
panel.add(lblFileName);
lblFileName.setFont(new Font("Tahoma", Font.BOLD, 11));

lblFileSize = new JLabel("Size:");
lblFileSize.setBounds(20, 30, 247, 14);
panel.add(lblFileSize);
lblFileSize.setFont(new Font("Tahoma", Font.BOLD, 11));

lblImagesCount = new JLabel("Total images to copy:");
lblImagesCount.setBounds(20, 49, 247, 14);
panel.add(lblImagesCount);
lblImagesCount.setFont(new Font("Tahoma", Font.BOLD, 11));

JButton btnCopyBack = new JButton("Copy to location");
btnCopyBack.setEnabled(false);
btnCopyBack.setFont(new Font("Tahoma", Font.PLAIN, 12));
btnCopyBack.setBounds(10, 165, 200, 23);
imgCleanerTab.add(btnCopyBack);

JButton btnDelete = new JButton("Clean images folder");
btnDelete.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    btnCopyBack.setEnabled(true);
    }
});
btnDelete.setEnabled(false);
btnDelete.setFont(new Font("Tahoma", Font.PLAIN, 12));
btnDelete.setBounds(10, 131, 200, 23);
imgCleanerTab.add(btnDelete);

JButton btnCopyImagesToTemp = new JButton("Copy images to temp folder");
btnCopyImagesToTemp.setEnabled(false);
btnCopyImagesToTemp.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    try {
        copyService.copyImagesToTempFolder();
        btnDelete.setEnabled(true);
    } catch (IOException e1) {
        JOptionPane.showMessageDialog(_contentPane, "File error " + e1.getMessage(), "Error",
            JOptionPane.ERROR_MESSAGE);
    }
    }
});
btnCopyImagesToTemp.setFont(new Font("Tahoma", Font.PLAIN, 12));
btnCopyImagesToTemp.setBounds(10, 97, 200, 23);
imgCleanerTab.add(btnCopyImagesToTemp);

JButton btnFileList = new JButton("Images List");
btnFileList.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
    new FileList(copyService.getFilePaths()).setVisible(true);
    }
});
btnFileList.setEnabled(false);
btnFileList.setBounds(276, 7, 123, 23);
panel.add(btnFileList);

JButton btnLoadTxtFile = new JButton("Load .txt File");
btnLoadTxtFile.addActionListener(new ActionListener() {
    @SuppressWarnings("static-access")
    public void actionPerformed(ActionEvent e) {
    try {
        JFileChooser jf = new JFileChooser("C:\\");
        FileNameExtensionFilter filter = new FileNameExtensionFilter("TEXT FILES", "txt", "text");
        jf.setFileFilter(filter);
        int status = jf.showOpenDialog(_contentPane);
        if (status == jf.APPROVE_OPTION) {
        copyService = new CopyService();
        copyService.loadTxtFile(jf.getSelectedFile());
        lblFileName.setText("Filename:" + " " + copyService.getFileProperties().get("filename"));
        lblFileSize.setText("Size:" + " " + copyService.getFileProperties().get("filesize") + " b");
        lblImagesCount.setText(
            "Total images to copy:" + " " + copyService.getFileProperties().get("totalimgs"));
        btnFileList.setEnabled(true);
        btnCopyImagesToTemp.setEnabled(true);

        } else {
        JOptionPane.showMessageDialog(_contentPane, "Error while choosing file", "Error",
            JOptionPane.ERROR_MESSAGE);
        }

    } catch (Exception e1) {
        JOptionPane.showMessageDialog(_contentPane, "File error " + e1.getMessage(), "Error",
            JOptionPane.ERROR_MESSAGE);
    }
    }
});
btnLoadTxtFile.setBounds(277, 45, 123, 23);
panel.add(btnLoadTxtFile);

JProgressBar progressBar = new JProgressBar();
progressBar.setBounds(10, 199, 409, 23);
imgCleanerTab.add(progressBar);

JPanel confifgTab = new JPanel();
tabbedPane.addTab("Settings", null, confifgTab, null);
confifgTab.setLayout(null);

JButton btnSaveSettings = new JButton("Save Settings");
btnSaveSettings.setFont(new Font("Tahoma", Font.BOLD, 12));
btnSaveSettings.setBounds(294, 202, 125, 20);
confifgTab.add(btnSaveSettings);

txtRootLocation = new JTextField();
txtRootLocation.setEditable(false);
txtRootLocation.setBounds(10, 34, 259, 20);
confifgTab.add(txtRootLocation);
txtRootLocation.setColumns(10);

JLabel lblNewLabel = new JLabel("Root folder for images:");
lblNewLabel.setBounds(10, 11, 140, 14);
confifgTab.add(lblNewLabel);

JButton btnSelectRootFolder = new JButton("Select Folder");
btnSelectRootFolder.setBounds(279, 33, 140, 23);
confifgTab.add(btnSelectRootFolder);

txtImgDestination = new JTextField();
txtImgDestination.setEditable(false);
txtImgDestination.setColumns(10);
txtImgDestination.setBounds(10, 88, 259, 20);
confifgTab.add(txtImgDestination);

JLabel lblDestinationFolderFor = new JLabel("Destination folder for images:");
lblDestinationFolderFor.setBounds(10, 65, 259, 14);
confifgTab.add(lblDestinationFolderFor);

JButton btnSelectDestFolder = new JButton("Select Folder");
btnSelectDestFolder.setBounds(279, 87, 140, 23);
confifgTab.add(btnSelectDestFolder);
}

public JTextField getTxtImgDestination() {
return txtImgDestination;
}

public JTextField getTxtRootLocation() {
return txtRootLocation;
}}

I want to show the copy progress on the progress bar, but I don't know how to implement it, I only know while copy method is in the same class as the gui.


Solution

  • You will want to allow outside classes to listen to the changes in the state of the CopyService class as it copies files, but fortunately Java and Swing has mechanisms for this, but it's going to require some effort on your part to learn how to use these libraries. Suggestsion:

    • Give the CopyService class a SwingPropertyChangeSupport private instance field, and initialize the instance, passing in this, the current class. This object has mechanics that allow it to accept listeners, and then you the coder can notify these listeners of changes to states in that class by calling its methods.
    • Give the class a public void addPropertyChangeListener(PropertyChangeListener l) delegation method, and inside the method, add the listener to the SwingPropertyChangeSupport instance.
    • Give the class a public constant String, say public static final String COPY = "copy";
    • Inside of the copyImagesToTempFolder method, increment a counter variable and then notify all listeners that have registered with your SwingPropertyChangeSupport instance by calling its firePropertyChange methods. The Bound Properties Tutorial can help you with the details.
    • Have your MainWindow class register a listener to its CopyService instance by calling its addPropertyChangeListener(...) method and passing in a valid PropertyChangeListener (again check the tutorial linked to above).
    • Inside this property change listener, get the state change information and use it to update the JProgressBar.
    • Important, call the copyImagesToTempFolder(...) method in a background thread -- use a SwingWorker for this, and this tutorial will help you with this: Lesson: Concurrency in Swing. If you don't use a background thread, then the file change process will block the Swing event thread, effectively freezing your GUI and preventing your progress bar from progressing.

    Another and perhaps better option is to have your CopyService class extend SwingWorker, and then use the same listener mechanisms that are already wired within the SwingWorker to achieve your goals.

    For example:

    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class ProgressMcve extends JPanel {
        private JProgressBar progressBar = new JProgressBar(0, 100);
        private CopyAction copyAction = new CopyAction();
    
        public ProgressMcve() {
            progressBar.setStringPainted(true);
    
            add(progressBar);
            add(new JButton(copyAction));
        }
    
        private class CopyAction extends AbstractAction {
            public CopyAction() {
                super("Copy");
                putValue(MNEMONIC_KEY, KeyEvent.VK_C);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                setEnabled(false);
                progressBar.setValue(0);
                MockCopyService copyService = new MockCopyService();
                copyService.addPropertyChangeListener(new CopyServiceListener());
                copyService.execute();
            }
        }
    
        private class CopyServiceListener implements PropertyChangeListener {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("progress".equals(evt.getPropertyName())) {
                    int progress = (int) evt.getNewValue();
                    progressBar.setValue(progress);
                } else if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
                    progressBar.setValue(100);
                    progressBar.setString("Done");
                    copyAction.setEnabled(true);
                    try {
                        ((MockCopyService) evt.getSource()).get();
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        private static void createAndShowGui() {
            ProgressMcve mainPanel = new ProgressMcve();
    
            JFrame frame = new JFrame("ProgressMcve");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }
    

    // simple MCVE example class that mocks the functioning of your class
    public class MockCopyService extends SwingWorker<Void, Integer> {
        public static final String COPY = "copy";
        private List<File> fileList = new ArrayList<>();
    
        public MockCopyService() {
    
        }
    
        public int getFileListSize() {
            // the real return:
            // return fileList.size();
    
            // the mock return:
            return 10;
        }
    
        public void copyImagesToTempFolder() {
            for (int i = 0; i < getFileListSize(); i++) {
                System.out.println("copying file");
                int fileProgress = (100 * i) / getFileListSize();
                // notify listeners
                setProgress(fileProgress);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            setProgress(getFileListSize());
        }
    
        @Override
        protected Void doInBackground() throws Exception {
            copyImagesToTempFolder();
            return null;
        }
    }