5

I have a GUI which is quite heavy to build/initialize on the platform on which it runs.. Therefore I want to update progress while it initializes..

I have a small undecorated JDialog containing a JLabel and a JProgressBar which I want to update at specific places during initialization, however, because the event dispatch thead (as per Swing rules) is used to build/initialize the GUI, the progress is of course not updated until the EDT is idle again (i.e. initialization is finished)..

The JProgressBar I have gotten to redraw using "paintImmediately", but I can't seem to make it work properly for the JLabel and the dialog itself.. Is there any simple recommended/proven method to accomplish this?

cheers...

EDIT: Adding an example of what it is I'm trying to do; greatly simplified, of course.

private JLabel progressLabel;
private JProgressBar progressBar;

public static int main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showProgressDialog();
            progressLabel.setText("construct 1");

            constructSomeHeavyGUI();

            progressLabel.setText("construct 2");
            progressBar.setValue(33);

            constructSomeMoreHeavyGUI();

            progressLabel.setText("construct 3");
            progressBar.setValue(67);

            constructEvenMoreHeavyGUI();

            progressLabel.setText("done");
            progressBar.setValue(100);

            hideProgressDialog();

            showHeavyGUI();

        }
    });
}

the repaints caused by the calls to progressBar.setValue()/progressLabel.setText() above will of course get queued as long as the EDT is busy and result in a repaint after we are all done instead of updating along the way..

  • 1
    *"greatly simplified, of course"* To post the code that is greatly simplified for the person answering, post an [SSCCE](http://pscode.org/sscce.html) (of course). – Andrew Thompson Aug 16 '11 at 14:54

4 Answers4

3

I would suggest that by using SwingWorker , then you can update the JProgressBar correctly on EDT and without any freeze or isuees with Concurency in Swing,

there is another option by using Runnable#thread, but then you have to wrapp all output to the GUI into invokeLater();

for example:

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;

public class TestProgressBar {

    private static void createAndShowUI() {
        JFrame frame = new JFrame("TestProgressBar");
        frame.getContentPane().add(new TestPBGui().getMainPanel());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowUI();
            }
        });
    }

    private TestProgressBar() {
    }
}

class TestPBGui {

    private JPanel mainPanel = new JPanel();

    public TestPBGui() {
        JButton yourAttempt = new JButton("WRONG attempt to show Progress Bar");
        JButton myAttempt = new JButton("BETTER attempt to show Progress Bar");
        yourAttempt.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                yourAttemptActionPerformed();
            }
        });
        myAttempt.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                myAttemptActionPerformed();
            }
        });
        mainPanel.add(yourAttempt);
        mainPanel.add(myAttempt);
    }

    private void yourAttemptActionPerformed() {
        Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
        JDialog progressDialog = new JDialog(thisWin, "Uploading...");
        JPanel contentPane = new JPanel();
        contentPane.setPreferredSize(new Dimension(300, 100));
        JProgressBar bar = new JProgressBar(0, 100);
        bar.setIndeterminate(true);
        contentPane.add(bar);
        progressDialog.setContentPane(contentPane);
        progressDialog.pack();
        progressDialog.setLocationRelativeTo(null);
        Task task = new Task("Your attempt");
        task.execute();
        progressDialog.setVisible(true);
        while (!task.isDone()) {
        }
        progressDialog.dispose();
    }

    private void myAttemptActionPerformed() {
        Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
        final JDialog progressDialog = new JDialog(thisWin, "Uploading...");
        JPanel contentPane = new JPanel();
        contentPane.setPreferredSize(new Dimension(300, 100));
        final JProgressBar bar = new JProgressBar(0, 100);
        bar.setIndeterminate(true);
        contentPane.add(bar);
        progressDialog.setContentPane(contentPane);
        progressDialog.pack();
        progressDialog.setLocationRelativeTo(null);
        final Task task = new Task("My attempt");
        task.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equalsIgnoreCase("progress")) {
                    int progress = task.getProgress();
                    if (progress == 0) {
                        bar.setIndeterminate(true);
                    } else {
                        bar.setIndeterminate(false);
                        bar.setValue(progress);
                        progressDialog.dispose();
                    }
                }
            }
        });
        task.execute();
        progressDialog.setVisible(true);
    }

    public JPanel getMainPanel() {
        return mainPanel;
    }
}

class Task extends SwingWorker<Void, Void> {

    private static final long SLEEP_TIME = 4000;
    private String text;

    public Task(String text) {
        this.text = text;
    }

    @Override
    public Void doInBackground() {
        setProgress(0);
        try {
            Thread.sleep(SLEEP_TIME);// imitate a long-running task
        } catch (InterruptedException e) {
        }
        setProgress(100);
        return null;
    }

    @Override
    public void done() {
        System.out.println(text + " is done");
        Toolkit.getDefaultToolkit().beep();
    }
}

EDIT:

1) you showed another issues, why do you create lots of Top-Level Containers on Fly/Runtime, create only required numbers of Containers and re-use that by removeAll()

2) here is probably what you needed, all those JProgressBars in the JTable are pretty accesible and configurable

3) this is your paintImmediately(), that really reason why not painting any of Progress to the JLabel but using JProgressBar#setValue(int); instead,

Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • that won't work.. if you read my post closely, you will notice that I'm aware of all the Swing/EDT threading issues.. I can't put the long running code on a different thread as it is the GUI construction code, which must run on the EDT according to Swing rules.. What I'm after is actually an immediate way to (re)paint my progress dialog while I'm already running on the EDT.. – Jesper Matthiesen Aug 16 '11 at 14:05
  • 1) please don't use paintImmediately() for Conpound JComponets, only if you are able to create own UI for some JComponents, 2) can you shows (my curiosity) how you implements paintImmediately(), because this can be used only if EDT exists and you always need to test if(SwingUtilities.isEventDispatchThread()) 3) don't reinvent the wheel, JProgressbar can do that easilly by implemented methods – mKorbel Aug 16 '11 at 14:21
  • 1) ok, but what should I use then? 2)/3) please see the edit in my original post for an example. – Jesper Matthiesen Aug 16 '11 at 14:47
2

Move the long running code in a separate thread and use SwingUtilities.invokeAndWait or invokeLater to update GUI.

StanislavL
  • 56,971
  • 9
  • 68
  • 98
2

Either use SwingUtilities.invokeLater(...) as suggested by @StanislavL, or use SwingWorker.

See also:

Community
  • 1
  • 1
mre
  • 43,520
  • 33
  • 120
  • 170
2

It's possible that constructSome*HeavyGUI() really takes long enough to matter, but it's more likely that filling in the data model(s) is the problem. Instead, construct and show the empty GUI elements and launch one or more SwingWorker instances to marshal each element's data. There are related examples here and here.

Addendum: If the problem is instantiating components, and not loading data models, you can chain the calls to invokeLater(), as suggested in a comment below. If you're instantiating that many components, consider the flyweight pattern. JTable is a familiar example.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • it's not populating it with data that causes problems, since no data is fetched/populated during construction. – Jesper Matthiesen Aug 17 '11 at 08:28
  • I've not seen that. If you can't offload anything, you'll have to chain calls to `invokeLater()`. For example, let `constructHeavy1()` end with an `invokeLater()` of `constructHeavy2()`, etc. – trashgod Aug 17 '11 at 09:28
  • I've ended up with going the "paintImmediately" route as a lack of a better alternative. – Jesper Matthiesen Aug 18 '11 at 13:46