2

I'm trying to implement a global loading dialog... I want to call some static function to show the dialog and some static function to close it. In the meanwhile I'm doing some work in the main thread or in a sub thread...

I tried following, but the dialog is not updating... Just once at the end, before hiding it again, it updates...

    private static Runnable getLoadingRunable()
{
    if (loadingRunnable != null)
    {
        loadingFrame.setLocationRelativeTo(null);
        loadingFrame.setVisible(true);  
        return loadingRunnable;
    }

    loadingFrame = new JFrame("Updating...");
    final JProgressBar progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    final JPanel contentPane = new JPanel();
    contentPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    contentPane.setLayout(new BorderLayout());
    contentPane.add(new JLabel("Updating..."), BorderLayout.NORTH);
    contentPane.add(progressBar, BorderLayout.CENTER);
    loadingFrame.setContentPane(contentPane);
    loadingFrame.pack();
    loadingFrame.setLocationRelativeTo(null);
    loadingFrame.setVisible(true);  

    loadingRunnable = new Runnable() {
        public void run() {

            try {
                while (running) {
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    loadingFrame.setVisible(false);
                }
            });
        }
    };
    return loadingRunnable;
}

public static void showLoadingBar() {
    System.out.println("showLoadingBar");
    running = true;

    threadLoadingBar = new Thread(getLoadingRunable());
    threadLoadingBar.start();
}

public static void hideLoadingBar() {
    System.out.println("hideLoadingBar");
    running = false;
    threadLoadingBar = null;
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
prom85
  • 16,896
  • 17
  • 122
  • 242
  • 1
    How should it update? You don't do anything with this frame except displaying it and hiding it. Please provide an SSCCE exhibiting the problem. – JB Nizet Jan 01 '13 at 20:17
  • Why are you using a new runnable and not just a new JDialog or new JFrame? I normally create a new JDialog from my main class, then display it and dispose it when done. I keep 1 instance of that dialog class, so the values set in it are kept in that object. I've also set properties to do something like this. I have a window that updates Properties, then the main window reads those properties in. – Logan Jan 01 '13 at 20:18
  • it should update permanently, because of the `progressBar.setIndeterminate(true);`... and it should not do anything else... btw, the example is already there, maybe the problem was not stated exact enough... The window is showing, the inderminate progress bar is not showing nor updating, just once at the end... the window is shown correctly and hidden correctly... – prom85 Jan 01 '13 at 20:20
  • I am using a runnable, because I want that it works (AND updates itself) if I'm using it before doing some work in the main thread as well... or before starting another thread... – prom85 Jan 01 '13 at 20:22
  • Take a look at this: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/ – keuleJ Jan 01 '13 at 20:51

2 Answers2

9

Swing uses a single thread model for it's event dispatching (including paint updates), as such, you should never, ever perform any long running or blocking operations within the Event Dispatching Thread (EDT).

Swing is also not thread safe, meaning that you should never create or modify any UI component from outside the EDT.

The basic concept of a global progress dialog is to provide a framework that is not depended on the work that needs to be performed, this separates the areas of responsibility, the progress dialog being responsible for displaying the progress and the worker/engine being responsible for doing the actual work.

The simplest solution would be to use a SwingWorker. This provides mechanisms for executing long running tasks in a separate thread, state change and progress notification.

enter image description here

public class TestProgressDialog {

    public static void main(String[] args) {
        new TestProgressDialog();
    }

    public TestProgressDialog() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                SwingWorker worker = new SwingWorker() {
                    @Override
                    protected Object doInBackground() throws Exception {
                        for (int index = 0; index < 100; index++) {
                            Thread.sleep(50);
                            setProgress(index);
                        }
                        return null;
                    }

                };

                ProgressDialog.showProgress(null, worker);

                System.exit(0);

            }

        });
    }

    public static class ProgressDialog extends JDialog {

        private JLabel message;
        private JLabel subMessage;
        private JProgressBar progressBar;

        public ProgressDialog(Component parent, SwingWorker worker) {

            super(parent == null ? null : SwingUtilities.getWindowAncestor(parent));
            setModal(true);

            ((JComponent)getContentPane()).setBorder(new EmptyBorder(8, 8, 8, 8));

            message = new JLabel("Doing important stuff");
            subMessage = new JLabel("Go make you're self a cup of coffee...");
            progressBar = new JProgressBar();

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.insets = new Insets(2, 2, 2, 2);
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.WEST;
            add(message, gbc);

            gbc.gridy++;
            add(subMessage, gbc);

            gbc.gridy++;
            gbc.weightx = 1;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            add(progressBar, gbc);

            pack();

            worker.addPropertyChangeListener(new PropertyChangeHandler());
            switch (worker.getState()) {
                case PENDING:
                    worker.execute();
                    break;
            }

        }

        public static void showProgress(Component parent, SwingWorker worker) {

            ProgressDialog dialog = new ProgressDialog(parent, worker);
            dialog.setLocationRelativeTo(parent);
            dialog.setVisible(true);

        }

        public class PropertyChangeHandler implements PropertyChangeListener {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals("state")) {
                    SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
                    switch (state) {
                        case DONE:
                            dispose();
                            break;
                    }
                } else if (evt.getPropertyName().equals("progress")) {
                    progressBar.setValue((int)evt.getNewValue());
                }
            }

        }

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
6

If it doesn't animate, it means that you're doing work in the event dispatch thread while the loading frame is displayed. This background work should be done in another thread.

Here's a non-working example:

public static void main(String[] args) throws Exception {
    SwingUtilities.invokeLater(
        new Runnable() {
            @Override
            public void run() {
                try {
                    showLoadingBar();
                    Thread.sleep(10000L); // doing work in the EDT. Prevents the frame from animating
                    hideLoadingBar();
                }
                catch (InterruptedException e) {
                }
            }
        }
    );
}

Here's a working example:

public static void main(String[] args) throws Exception {
    showLoadingBar();
    Thread.sleep(10000L); // doing work outside of the EDT. Everything works fine
    hideLoadingBar();
}

Side note: the code which instantiates, populates and makes the loading frame visible should be wrapped into SwingUtilities.invokeLater(), because it must be run in the EDT.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255