简体   繁体   中英

Swing Progress Bar updates via Worker to EventDispatch thread

I have a JAVA6 GUI handling data import to our database. I have implemented a working JProgressBar. I understand that changes made to the GUI must be done via the event dispatch thread--which I do not think I am doing (properly/at all).

the background Worker thread, UploadWorker, is constructed by passing in the a JProgressBar created in the main program, and sets changes the value of the progress bar directly once it is finished:

// when constructed, this gets set to the main program's JProgressBar.
JProgressBar progress; 



protected Void doInBackground() throws Exception {
    write("<!-- Import starting at " + getCurrentTime() + " -->\n");
    boolean chunked = false;
    switch (importMethod) {

            //do some importing

    }

    write("<!-- Import attempt completed at " + getCurrentTime() + "-->\n");

    //here changes to the GUI are made
    progress.setMaximum(0);
    progress.setIndeterminate(false);
    progress.setString("Finished Working");
    return null;
}

This works fine, but sometimes(not always) throws me several NPE's in the std out, and users are complaining:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.plaf.basic.BasicProgressBarUI.updateSizes(Unknown Source)
...etc...

Anyway, I believe there is something I need to do to get these updates executed on the proper thread, correct? How?

There are a number of ways you could do this, you could use the process method of the SwingWorker to also update the progress bar, but for me, this couples your worker to the UI, which isn't always desirable.

A better solution is to take advantage of the SwingWorker s progress and PropertyChange support, for example....

worker.addPropertyChangeListener(new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("state".equalsIgnoreCase(evt.getPropertyName())) {
            SwingWorker worker = (SwingWorker) evt.getSource();
            switch (worker.getState()) {
                case DONE:
                    // Clean up here...
                    break;
            }
        } else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
            // You could get the SwingWorker and use getProgress, but I'm lazy... 
            pb.setIndeterminate(false);
            pb.setValue((Integer)evt.getNewValue());
        }
    }
});
worker.execute();

This means you could do this for ANY SwingWorker , so long as it was the worker was calling setProgress internally...

public static class ProgressWorker extends SwingWorker {

    public static final int MAX = 1000;

    @Override
    protected Object doInBackground() throws Exception {
        for (int index = 0; index < MAX; index++) {
            Thread.sleep(250);
            setProgress(Math.round((index / (float)MAX) * 100f));
        }
        return null;
    }

}

The benefit of this is that the PropertyChange event notification is called from within the context of the of Event Dispatching Thread, making it safe to update the UI from within.

And fully runnable example...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class SwingWorkerProgressExample {

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

    public SwingWorkerProgressExample() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JProgressBar pb;

        public TestPane() {

            setLayout(new GridBagLayout());
            pb = new JProgressBar(0, 100);
            pb.setIndeterminate(true);
            add(pb);

            ProgressWorker worker = new ProgressWorker();
            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("state".equalsIgnoreCase(evt.getPropertyName())) {
                        SwingWorker worker = (SwingWorker) evt.getSource();
                        switch (worker.getState()) {
                            case DONE:
                                // Clean up here...
                                break;
                        }
                    } else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
                        // You could get the SwingWorker and use getProgress, but I'm lazy... 
                        System.out.println(EventQueue.isDispatchThread());
                        pb.setIndeterminate(false);
                        pb.setValue((Integer) evt.getNewValue());
                    }
                }
            });
            worker.execute();

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

    public static class ProgressWorker extends SwingWorker {

        public static final int MAX = 1000;

        @Override
        protected Object doInBackground() throws Exception {
            for (int index = 0; index < MAX; index++) {
                Thread.sleep(250);
                setProgress(Math.round((index / (float) MAX) * 100f));
            }
            return null;
        }

    }

}

您可以只创建一个执行GUI更新的新Runnable,然后使用SwingUtilities.invokeLater在GUI线程中调用它。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM