javaswingmethodsevent-dispatch-threadjprogressbar

How to run method with JProgressBar


I have a function called CreateAccount. I need it to run and also need to show a progress bar. When I click the button, the method will start. And I need to start showing loading progress bar. Once method is done progress bar also should stop at 100. If the method gets more time to do the job, progress bar also needs to load slowly.

I tried using following code but it is not synchronizing progress bar with the method. So how can I do that?

Here is my code:

private static int t = 0;

private void createAccountBtnActionPerformed(java.awt.event.ActionEvent evt) {                                                 
        progressBar.setValue(0);
        progressBar.setStringPainted(true);

        new Thread(new Runnable() {
            @Override
            public void run() {
                //CreateAccount();

                for (t = 0; t <= 100; t++) {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            CreateAccount();
                            progressBar.setValue(t);
                        }
                    });
                    try {
                        java.lang.Thread.sleep(100);
                    }
                    catch(InterruptedException e) { }
                }
            }
        }).start();
    }

Solution

  • Because of the single threaded nature of Swing, you can't perform long running or blocking operations from within the context of the Event Dispatching Thread, nor can you update the UI from outside the context of the Event Dispatching Thread.

    See Concurrency in Swing for more details.

    Both these things you are taking care of. The problem is, this means that it's possible for the background thread to do more work than is been presented on the UI and there's nothing you can do about. The the best bet is simply trying too keep the UI up-to-date as much as possible

    A possible better solution might be to use a SwingWorker, which is designed to make updating the UI easier. See Worker Threads and SwingWorker for more details.

    The following example shows a progress bar which will run for 10 seconds with a random delay of up to 500 milliseconds between each update. The progress bar is then update based on the amount of time remaining.

    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.time.Duration;
    import java.time.Instant;
    import java.util.Random;
    import javax.swing.JButton;
    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 final class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            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;
            private JButton btn;
    
            public TestPane() {
                setLayout(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridwidth = GridBagConstraints.REMAINDER;
    
                btn = new JButton("Go");
                pb = new JProgressBar();
    
                add(btn, gbc);
                add(pb, gbc);
    
                btn.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        btn.setEnabled(false);
                        makeItProgress();
                    }
                });
            }
    
            protected void makeItProgress() {
                SwingWorker<Double, Double> worker = new SwingWorker<Double, Double>() {
                    @Override
                    protected Double doInBackground() throws Exception {
                        Duration duration = Duration.ofSeconds(10);
                        Instant startTime = Instant.now();
                        Duration runningTime = Duration.ZERO;
                        Random rnd = new Random();
                        setProgress(0);
                        do {
                            Thread.sleep(rnd.nextInt(500));
                            Instant now = Instant.now();
                            runningTime = Duration.between(startTime, now);
    
                            double progress = (double) runningTime.toMillis() / (double) duration.toMillis();
                            setProgress((int) (progress * 100));
                        } while (duration.compareTo(runningTime) >= 0);
                        setProgress(100);
                        return 1.0;
                    }
                };
                worker.addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        SwingWorker worker = (SwingWorker) evt.getSource();
                        if (evt.getPropertyName().equals("progress")) {
                            int value = (int) evt.getNewValue();
                            pb.setValue(value);
                        } else if (evt.getPropertyName().equals("state") && worker.getState() == SwingWorker.StateValue.DONE) {
                            pb.setValue(100);
                            btn.setEnabled(true);
                        }
                    }
                });
                worker.execute();
            }
    
        }
    
    }
    

    The point of this example is, the progress and the work are mixed into a single operation (the doInBackground method of the SwingWorker) so they are more closely related. The SwingWoker then notifies the PropertyChangeListener of updates, to which it can react to safely on the Event Dispatching Thread