1

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?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
FiddleBug
  • 177
  • 2
  • 11
  • The point of using a `SwingWorker` is that you `publish` the "updates" back to the Event Dispatching Queue and `process` those changes safely. You also shouldn't be changing the size of the component – MadProgrammer May 11 '21 at 21:33
  • `SwingWorker` also has it's own progress support, for [example](https://stackoverflow.com/questions/13094666/jprogressbar-wont-update/13095992#13095992), [example](https://stackoverflow.com/questions/18835835/jprogressbar-not-updating/18837930#18837930), [example](https://stackoverflow.com/questions/12020949/jprogressbar-isnt-progressing/12021971#12021971), [example](https://stackoverflow.com/questions/13846887/jprogressbar-too-fast/13846939#13846939) – MadProgrammer May 11 '21 at 21:37
  • [example](https://stackoverflow.com/questions/17101372/how-to-include-a-progress-bar-in-a-program-of-file-transfer-using-sockets-in-jav/17101753#17101753), [example](https://stackoverflow.com/questions/14097149/how-to-make-a-jprogressbar-to-run-in-parallel-with-my-algorithm/14098406#14098406) – MadProgrammer May 11 '21 at 21:37

2 Answers2

1

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;
            }
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I think i failed to mention this, so i'll mention it now. Async task doesn't have any intermediate results. It has a method call that takes a long time to complete. I am trying to update the progress bar independent of the task. – FiddleBug May 12 '21 at 16:28
  • Sure, so `SwingWorker` "can" do that, and generally can do it easier than `SwingUtilities.invokeLater`, generally. My "personal" gut feeling would be to define a workflow that allowed you to pass a progress observer to the task, so the task itself could provide progress feedback so as to give a better indication of the amount of work completed, but we'd need more information about what's going on. – MadProgrammer May 12 '21 at 19:16
  • When you say "task", do you mean the method call that is taking a long time? What information do you need? The long running method call does network scanning where it scans different subnets to find out network devices on the network. I am currently trying to ascertain if there is a way i can show progress by finding out the number of subnets scanned versus total number of subnets. – FiddleBug May 12 '21 at 19:46
  • @FiddleBug Yes, what ever `asyncTask()` is. In order to realistically show progress, you need to know the amount of work to be done and the amount of work remaining – MadProgrammer May 12 '21 at 21:39
  • 1
    @FiddleBug Look at it from a point of responsibility. It's not the "workers" responsibility to determine the amount of progression, it's the tasks. Now, either the task is going to do this in a direct manner, such as `getSubNetsScanned` and `getTotalSubNets` or indirectly via some kind of observer. From there, you need to determine the best workflow to use to update the UI. You could use a `SwingWorker` in with a "direct" workflow, as you'd need to constantly poll the state of the task or `SwingUtiltiites#invokeLater` for the observer (as the tasks should be decoupled) – MadProgrammer May 12 '21 at 22:05
0

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();
        }
    }
}
Rocco
  • 1,098
  • 5
  • 11
  • Why the first example? The point of a `SwingWorker` is to use the `publish` and `process` workflow to allow you to sync updates to the UI safety. `SwingWorker` also has a `progress` property which can be used the same way as the second example, so, you're reinventing the wheel, twice - not saying you can't do it that way, but why bother :P – MadProgrammer May 12 '21 at 09:06
  • @The first example is just to show that the OP code, even if the progress bar is updated, will not work anyway. Maybe a little bit too criptic ... here there is no need at all to have a SwingWorker involved, since it's progress must depend on the async task progress, and will be just a duplicate. – Rocco May 12 '21 at 09:31
  • True, without the details of the task it's difficult to ascertain a course of action, personally, I be tempted to extend the progress concept into the task itself, so it could feedback - but that might going beyond the scope - thanks for the update – MadProgrammer May 12 '21 at 09:35