Java GUI - Progress bar doesn't update until the async task is finished

909 Views Asked by At

I am using CompletableFuture to run a long running operation. Meanwhile, i use SwingWorker to update a progress bar with increments of 5.

            JProgressBar progressBar = new JProgressBar();
            progressBar.setMinimum(0);
            progressBar.setMaximum(100);
            progressBar.setValue(0);
            progressBar.setStringPainted(true);
            CompletableFuture<NetworkToolsTableModel> completableFuture = CompletableFuture.supplyAsync(() -> asyncTask());
            
            SwingWorker worker = new SwingWorker() {
                @Override
                protected Object doInBackground() throws Exception {
                    int val = 0;
                    while (!completableFuture.isDone()) {
                        if (val < 100) {
                            val += 5;
                            progressBar.setValue(val);
                        }
                        Rectangle bounds = progressBar.getBounds(null);
                        progressBar.paintImmediately(bounds);
                    }
                    return null;
                }
            };
            worker.execute();

The progress bar doesn't update until the asynchronous method is finished. I have also tried doing this operation on the EDT thread, but to no avail. What you are seeing is basically me trying to just do trial and error at this point.

Why is the progress bar not updating? How can I fix this?

2

There are 2 best solutions below

5
MadProgrammer On BEST ANSWER

Stop and take a closer look at Worker Threads and SwingWorker.

A SwingWorker is meant to allow you to perform a long running task, which may produce intermediate results, and allow you to publish those results back to the Event Dispatching Thread to be safely processed (via the process method).

You "could" publish the progress updates and update the progress bar in process method, but SwingWorker already provides a progress property, which you can monitor. Take a look at the SwingWorker JavaDocs for a number of ready made examples!

Run Example

Simple Example

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;

public 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 ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                ProgressPane progressPane = new ProgressPane();
                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(progressPane);
                frame.setSize(200, 200);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

//                progressPane.doWork();
            }
        });
    }

    public class ProgressPane extends JPanel {

        private JProgressBar progressBar;
        private JButton startButton;

        public ProgressPane() {

            setLayout(new GridBagLayout());
            progressBar = new JProgressBar();

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            add(progressBar, gbc);
            startButton = new JButton("Start");
            gbc.gridy = 1;
            add(startButton, gbc);

            startButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    startButton.setEnabled(false);
                    doWork();
                }
            });

        }

        public void doWork() {

            Worker worker = new Worker();
            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("progress".equals(evt.getPropertyName())) {
                        progressBar.setValue((Integer) evt.getNewValue());
                    }
                }
            });

            worker.execute();

        }

        public class Worker extends SwingWorker<Object, Object> {

            @Override
            protected void done() {
                startButton.setEnabled(true);
            }

            @Override
            protected Object doInBackground() throws Exception {

                for (int index = 0; index < 1000; index++) {
                    int progress = Math.round(((float) index / 1000f) * 100f);
                    setProgress(progress);

                    Thread.sleep(10);
                }

                return null;
            }
        }
    }
}
3
Rocco On

This will update the progress bar, but it will immediately reach 100% since the SwingWorker is looping continuously.

You should get the actual progress value from the async task, either by polling the task or (better) using the observer pattern.

In the latter case you can remove the swing worker and directly update the progress bar in the observer callback method.

    SwingWorker worker = new SwingWorker() {
        @Override
        protected Object doInBackground() throws Exception {
            int val = 0;
            while (!completableFuture.isDone()) {
                if (val < 100) {
                    val += 5;
                    
                    final int prog=val;
                    SwingUtilities.invokeLater(()->{progressBar.setValue(prog); });
                }
            }
            return null;
        }
    };

Here a complete example with observer pattern, I defined an observable class ALongTask using PropertyChangeSupport, the task notifies its progress value to all registered observers (i.e. PropertyChangeListener).

package test;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;

public class TestProgressBar {


    public static void main(String[] args) {
        JProgressBar progressBar = new JProgressBar();
        progressBar.setMinimum(0);
        progressBar.setMaximum(100);
        progressBar.setValue(0);
        progressBar.setStringPainted(true);
        ALongTask asyncTask=new ALongTask();
        asyncTask.addPropertyChangeListener((e)-> { SwingUtilities.invokeLater(()->{progressBar.setValue((Integer) e.getNewValue()); }); });

        CompletableFuture.supplyAsync(asyncTask);

        JFrame frame=new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(progressBar);
        frame.pack();
        frame.setVisible(true);
    }
    
    public static class ALongTask implements Supplier<Object> {
        PropertyChangeSupport support=new PropertyChangeSupport(this);
        protected int progress;
        
        public void setProgress(int progress) {
            int old=this.progress;
            this.progress=progress;
            support.firePropertyChange("progress", old, progress);
        }
        
        public void addPropertyChangeListener(PropertyChangeListener l) {
            support.addPropertyChangeListener(l);
        }
        
        public void removePropertyChangeListener(PropertyChangeListener l) {
            support.removePropertyChangeListener(l);
        }

        @Override
        public Object get() {
            for (int i=0;i<20;i++) {
                setProgress(i*5);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    return null;
                }
            }
            setProgress(100);
            return new Object();
        }
    }
}