Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update/paint JProgressBar while Swing is loaded building the GUI

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..

like image 643
Jesper Matthiesen Avatar asked Aug 16 '11 13:08

Jesper Matthiesen


4 Answers

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,

like image 58
mKorbel Avatar answered Nov 15 '22 01:11

mKorbel


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.

like image 24
trashgod Avatar answered Nov 15 '22 02:11

trashgod


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

like image 2
StanislavL Avatar answered Nov 15 '22 02:11

StanislavL


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

See also:

  • Worker Threads and SwingWorker
like image 2
mre Avatar answered Nov 15 '22 03:11

mre