jProgressBar update from SwingWorker
Asked Answered
A

4

13

I use to monitor a long running task by updating a ProgressBar. The long running task is of course performed in a Swingworker thread.

I used to program things like that :

public class MySwingWorkerClass extends SwingWorker<Void, Void> {   
    private JProgressBar progressBar;    

    public MySwingWorker(JProgressBar aProgressBar) {        
        this.progressBar = aProgressBar;           
        progressBar.setVisible(true);        
        progressBar.setStringPainted(true);
        progressBar.setValue(0);        
    }

    @Override
    public Void doInBackground() {
        //long running task
        loop {  
            calculation();
            progressBar.setValue(value);
        }
        return null;
    }    

    @Override
    public void done() {                
        progressBar.setValue(100);
        progressBar.setStringPainted(false);
        progressBar.setVisible(false);      
   }
}

but recently I discovered that I could do it by using the "setProgress" and defining the property change and do things like that

public class MySwingWorkerClass extends SwingWorker<Void, Void> {   
    private JProgressBar progressBar;    

    public MySwingWorker(JProgressBar aProgressBar) {        
        addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if ("progress".equals(evt.getPropertyName())) {
                    progressBar.setValue((Integer) evt.getNewValue());
                }
            }
        });

        progressBar.setVisible(true);        
        progressBar.setStringPainted(true);
        progressBar.setValue(0);
        setProgress(0);
    }

    @Override
    public Void doInBackground() {
        //long running task
        loop {  
            calculation();
            setProgress(value);
        }
        return null;
    }    

    @Override
    public void done() {                
        setProgress(100);
        progressBar.setValue(100);
        progressBar.setStringPainted(false);
        progressBar.setVisible(false);      
   }
}

My question is : is my first code acceptable or shall I use the setProgress for anyreason ? I find the second code more complicated and in my case and don't know if there is any advantage or reason to use the second one.

Any advise ?

EDIT Thanks for the answer. As a summary. First solution is "wrong" because of the progress bar update is performed outside the EDT. Second solution is "correct" because the progress bar update is performed inside the EDT

Now, according to the "interesting" answer of @mKorbel in my case my calculation give results in HTML text which I "insert" (see this link). My current code is the following.

I publish(string) and my process code looks like that

@Override
    protected void process(List<String> strings) {
        for (String s : strings) {
            try {
                htmlDoc.insertBeforeEnd(htmlDoc.getElement(htmlDoc.getDefaultRootElement(), StyleConstants.NameAttribute, HTML.Tag.TABLE), s);
            } catch (BadLocationException ex) {
            } catch (IOException ex) {
            }
        }
    }

How can I reuse @mKobel to do the same in my case. I mean he use to override table rendering in my case what renderer shall I override (jTextPane?) and how ?

Alltime answered 27/5, 2012 at 11:15 Comment(5)
I think your second approach is wrong as well. You are using a listener to set the value, which will invoke the listener, which will set the value, which will invoke the listener, etc etc. I'm not sure if this actually will happen, but it feels wrong. This way, you are not getting the benefit of the SwingWorker, you are still setting the value of the progress bar in the EDT.Lightfoot
SwingWorker.publish(V...) "This method is to be used from inside the doInBackground method to deliver intermediate results for processing on the Event Dispatch Thread inside the process method."Wagtail
@Andrews Thompson : I use the publish to "publish" results but here docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html to set a progressbar value they use setProgress and NOT publish, so ?Alltime
@MartijnCourteaux : I am not sure to understand. I used this docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html to write the listener. Can you provide me code to explain what you are saying.Alltime
@trashgod: Oh, yes I see. I didn't notice that the listener was added to the SwingWorker instead of the ProgressBar. Thank you! Learnt something :DLightfoot
C
7

In the first code, you are calling the following line in a non-EDT (Event Dispatcher Thread) thread. So it is not thread safe:

progressBar.setValue(value);

This may result in unexpected behaviour as Swing is not designed as a thread-safe library.

There are different methods to perform this in the Swing way. One correct way of this is what you have done in the second post. Another would be to use publish()/process() methods, and a third method would be writing your own thread instead of SwingWorker and using SwingUtilities.invokeLater().

Colum answered 27/5, 2012 at 11:19 Comment(1)
Can you Edit your answer to say if you mean my second code is the one to use ? because comments say it is "wrong" but I though it was right according to this docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html shall I use the "publish" to update the progressbar ? I thought setprogress what there for this.Alltime
M
5

Your second approach is correct and is even documented in the class javadoc of the SwingWorker class. The 'progress' event is fired on the EDT, so your listener updates the progress bar on the EDT. This is not the case in your first approach.

An example of another approach (using publish/process as indicated by vizier) can be found in my answer on a previous SO question

Muddy answered 27/5, 2012 at 11:59 Comment(2)
The link you provided is interesting I like the way you update the progress bar in the process/publish and not by using the setProgress. Will have a closer look at this.Alltime
Robin's helpful example shows the convenience of directly coupling the worker and the progress bar; the advantage of loosely coupling via PropertyChangeListener is that all listeners are notified on the EDT, not just the progress bar.Sync
L
5

I use to monitor a long running task by updating a ProgressBar. The long running task is of course performed in a Swingworker thread.

right you can use SwingWorker in all cases for redirecting any heavy and long running task to the Background

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableCellProgressBar {

    private String[] columnNames = {"String", "ProgressBar"};
    private Object[][] data = {{"dummy", 100}};
    private DefaultTableModel model = new DefaultTableModel(data, columnNames) {

        private static final long serialVersionUID = 1L;

        @Override
        public Class<?> getColumnClass(int column) {
            return getValueAt(0, column).getClass();
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            return false;
        }
    };
    private JTable table = new JTable(model);

    public JComponent makeUI() {
        TableColumn column = table.getColumnModel().getColumn(1);
        column.setCellRenderer(new ProgressRenderer());
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                startTask("test");
                startTask("error test");
                startTask("test");
            }
        });
        JPanel p = new JPanel(new BorderLayout());
        p.add(new JScrollPane(table));
        return p;
    }
//http://java-swing-tips.blogspot.com/2008/03/jprogressbar-in-jtable-cell.html

    private void startTask(String str) {
        final int key = model.getRowCount();
        SwingWorker<Integer, Integer> worker = new SwingWorker<Integer, Integer>() {

            private int sleepDummy = new Random().nextInt(100) + 1;
            private int lengthOfTask = 120;

            @Override
            protected Integer doInBackground() {
                int current = 0;
                while (current < lengthOfTask && !isCancelled()) {
                    if (!table.isDisplayable()) {
                        break;
                    }
                    if (key == 2 && current > 60) { //Error Test
                        cancel(true);
                        publish(-1);
                        return -1;
                    }
                    current++;
                    try {
                        Thread.sleep(sleepDummy);
                    } catch (InterruptedException ie) {
                        break;
                    }
                    publish(100 * current / lengthOfTask);
                }
                return sleepDummy * lengthOfTask;
            }

            @Override
            protected void process(java.util.List<Integer> c) {
                model.setValueAt(c.get(c.size() - 1), key, 1);
            }

            @Override
            protected void done() {
                String text;
                int i = -1;
                if (isCancelled()) {
                    text = "Cancelled";
                } else {
                    try {
                        i = get();
                        text = (i >= 0) ? "Done" : "Disposed";
                    } catch (Exception ignore) {
                        ignore.printStackTrace();
                        text = ignore.getMessage();
                    }
                }
                System.out.println(key + ":" + text + "(" + i + "ms)");
            }
        };
        model.addRow(new Object[]{str, 0});
        worker.execute();
    }

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

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

    public static void createAndShowGUI() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.getContentPane().add(new TableCellProgressBar().makeUI());
        frame.setSize(320, 240);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

class ProgressRenderer extends DefaultTableCellRenderer {

    private final JProgressBar b = new JProgressBar(0, 100);

    public ProgressRenderer() {
        super();
        setOpaque(true);
        b.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        Integer i = (Integer) value;
        String text = "Completed";
        if (i < 0) {
            text = "Error";
        } else if (i < 100) {
            b.setValue(i);
            return b;
        }
        super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);
        return this;
    }
}

but why complicating Wwing GUI by using SwingWorker (required deepest knowledge about Java Essential Classes and Generics too),

Basic implementations for Runnable#Thread required only invokeLater for output to the Swing GUI, and in the case that started from EDT (from Swing/AWT Listener), and without any code line contains Thread.sleep(int) then is invokeLater only adviced / required for production code

import java.awt.Component;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;

public class TableWithProgressBars {

    public static class ProgressRenderer extends JProgressBar implements TableCellRenderer {

        private static final long serialVersionUID = 1L;

        public ProgressRenderer(int min, int max) {
            super(min, max);
            this.setStringPainted(true);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
            this.setValue((Integer) value);
            return this;
        }
    }
    private static final int maximum = 100;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TableWithProgressBars().createGUI();
            }
        });

    }

    public void createGUI() {
        final JFrame frame = new JFrame("Progressing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Integer[] oneRow = {0, 0, 0, 0};
        String[] headers = {"One", "Two", "Three", "Four"};
        Integer[][] data = {oneRow, oneRow, oneRow, oneRow, oneRow,};
        final DefaultTableModel model = new DefaultTableModel(data, headers);
        final JTable table = new JTable(model);
        table.setDefaultRenderer(Object.class, new ProgressRenderer(0, maximum));
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        frame.add(new JScrollPane(table));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        new Thread(new Runnable() {

            @Override
            public void run() {
                Object waiter = new Object();
                synchronized (waiter) {
                    int rows = model.getRowCount();
                    int columns = model.getColumnCount();
                    Random random = new Random(System.currentTimeMillis());
                    boolean done = false;
                    while (!done) {
                        int row = random.nextInt(rows);
                        int column = random.nextInt(columns);
                        Integer value = (Integer) model.getValueAt(row, column);
                        value++;
                        if (value <= maximum) {
                            model.setValueAt(value, row, column);
                            try {
                                waiter.wait(15);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        done = true;
                        for (row = 0; row < rows; row++) {
                            for (column = 0; column < columns; column++) {
                                if (!model.getValueAt(row, column).equals(maximum)) {
                                    done = false;
                                    break;
                                }
                            }
                            if (!done) {
                                break;
                            }
                        }
                    }
                    frame.setTitle("All work done");
                }
            }
        }).start();
    }
}

my conclusion for real heavy and long running task you have look at Runnable#Thread (simple, easy, non_buggy and clear way), only if is your knowledge about Java & Swing very well then you can thinking about SwingWorker

Lanza answered 27/5, 2012 at 13:19 Comment(1)
this answer generates exceptions in my case see Question!Funderburk
S
4

As shown in this example, your use of the worker's setProgress() in your second example is correct: any PropertyChangeListener will be notified asynchronously on the event dispatch thread.

Sync answered 27/5, 2012 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.