Michael Groleau
Michael Groleau

Reputation: 91

JProgressBar is invisible but progress is updating correctly

I execute the task in this class and the Dialog pops up as a white box. The print statement IS printing out the progress values I'm expecting, but nothing shows up on the Dialog until after the operation is complete. I can see the progress bar flash visible for a millisecond before the dialog is closed at the end. Absolutely no clue what's going on :\

    public class ProgressDialog extends JDialog {

    private JProgressBar pb;
    private SwingWorker<Boolean, Void> task;

    public SwingWorker<Boolean, Void> getTask(){
        return task;
    }

    public ProgressDialog(final String call){
        setTitle("Working...");
        setLayout(new BorderLayout());
        setBounds(300,300,300,100);
        pb = new JProgressBar(0, 100);
        pb.setValue(0);
        pb.setVisible(true);
        pb.setStringPainted(true);
        add(pb, BorderLayout.CENTER);
        setVisible(true);

        task = new SwingWorker<Boolean, Void>(){
            public Boolean doInBackground(){        
                switch(call){
                case "Category": pb.setValue(Category.getProgress());
                while(pb.getValue()<99){
                    try{
                        Thread.sleep(500);
                    } catch (InterruptedException e){
                        Thread.currentThread().interrupt();
                    }
                    pb.setValue(Category.getProgress());
                    System.out.println(pb.getValue());
                    repaint();
                    revalidate();
                }
                break;
                }
                return true;
            }
            public void done(){
                dispose();
            }
        };
    }
}

EDIT: tried this change. no dice. Why am I not even getting a progress bar at 0%? It only appears once it is at 100%

public class ProgressDialog extends JDialog {

private JProgressBar pb;
private SwingWorker<Boolean, Integer> task;

public SwingWorker<Boolean, Integer> getTask(){
    return task;
}

public ProgressDialog(final String call){
    setTitle("Working...");
    setLayout(new BorderLayout());
    setBounds(300,300,300,100);
    pb = new JProgressBar(0, 100);
    pb.setValue(0);
    pb.setStringPainted(true);
    add(pb, BorderLayout.CENTER);
    setVisible(true);

    task = new SwingWorker<Boolean, Integer>(){
        public Boolean doInBackground(){        
            switch(call){
            case "Category": setProgress(Category.getProgress());
            while(pb.getValue()<99){
                try{
                    Thread.sleep(500);
                } catch (InterruptedException e){
                    Thread.currentThread().interrupt();
                }
                setProgress(Category.getProgress());
            }
            break;
            }
            return true;
        }

        public void done(){
            //dispose();
        }
    };

    task.addPropertyChangeListener(new PropertyChangeListener() {
                public  void propertyChange(PropertyChangeEvent evt) {
                    if ("progress".equals(evt.getPropertyName())) {
                        System.out.println((Integer)evt.getNewValue());
                        pb.setValue((Integer)evt.getNewValue());
                        pb.revalidate();
                        pb.repaint();
                    }
                }
            });
}

}

Upvotes: 0

Views: 606

Answers (2)

ryvantage
ryvantage

Reputation: 13479

Here's an SSCCE to demonstrate how you should be updating your JProgressBar. Copy/paste this and run it.

Notice how we update the progress bar by calling publish(i) which sends the integer to the process() method. The SwingWorker sends results to the process() method in chunks, but we are only using an Integer to update the JProgressBar so all we care about it the LAST chunk. In this SSCCE, we go from 1-1000. If you examine the console, you'll see that a lot of numbers between 1-1000 are being skipped because we are updating too fast for the SwingWorker to catch up (but that's ok. That's why it delivers results in chunks).

NOTE: the process() method was originally designed for programmers to return real-time results from their long-running processes and update the GUI. So, if you were doing a database fetch, you could update a JTable with the results you return. I hate doing things that way, though. So 99% of the time I just use an "indeterminate" JProgressBar and wait till the done() method to publish my results. Occaisionally, however, I'll use a "determinate" JProgressBar and update like we do in this SSCCE. Never have I used process() to return and publish actual data. :) But, that's what it was originally designed to do.

import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

/**
 *
 * @author Ryan
 */
public class Test {

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                go();
            }
        });
    }

    public static void go() {
        JFrame frame = new JFrame();
        JProgressBar jpb = new JProgressBar();
        jpb.setIndeterminate(false);
        int max = 1000;
        jpb.setMaximum(max);
        frame.add(jpb);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        new Task(jpb, max).execute();
    }

    static class Task extends SwingWorker<Void, Integer> {

        JProgressBar jpb;
        int max;
        public Task(JProgressBar jpb, int max) {
            this.jpb = jpb;
            this.max = max;
        }

        @Override
        protected void process(List<Integer> chunks) {
            jpb.setValue(chunks.get(chunks.size()-1)); // The last value in this array is all we care about.
            System.out.println(chunks.get(chunks.size()-1));
        }

        @Override
        protected Void doInBackground() throws Exception {
            for(int i = 0; i < max; i++) {
                Thread.sleep(10); // Sleep for 1/10th of a second
                publish(i);
            }
            return null;
        }

        @Override
        protected void done() {
            try {
                get();
                JOptionPane.showMessageDialog(jpb.getParent(), "Success", "Success", JOptionPane.INFORMATION_MESSAGE);
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

EDIT: I created a diagram that should be a helpful reference when handling SwingWorker so you know where to place your code.

enter image description here

Upvotes: 2

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285415

You're trying to set the progress bar's state from within the SwingWorker's doInBackground method, from a background thread -- which makes no sense. The whole reason for using a SwingWorker is to allow you to do a background process in a Swing GUI, so you don't make Swing calls from a background thread, and so that you don't tie up the Swing thread with a long-running bit of code.

You should not make Swing calls from this background process. Instead use the publish/process methods as the tutorials will show you. Or perhaps better, set the SwingWorker's progress field, and use a PropertyChangeListener on the SwingWorker to allow the progress bar to react to it.

Regardless, the bottom line:

  • Use the SwingWorker to do background work.
  • Do not make Swing calls from within the SwingWorker's doInBackground method.
  • Use publish to push data from the background method into the Swing thread realm.
  • Use the process method to handle this data being pushed.
  • SwingWorker has a progress property that is also handy to use for allowing Swing code to respond to changes in background states.
  • If you go this route, use a PropertyChangeListener.
  • You almost never want to use setBounds(...) or null layout. Trust me as someone who has written hundreds of Swing programs, this one will bite you in the end.
  • It looks as if your Category is using a static method for getting its progress. Again, this is something you almost never want to do. A progress field suggests state, and this should be part of the instance fields of an object, never static.

Upvotes: 3

Related Questions