SwingWorker更新jProgressBar

12

我以前使用进度条来监控长时间运行的任务。当然,这些长时间运行的任务都是在 Swingworker 线程中执行的。

我过去常常编写这样的程序:

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);      
   }
}

但最近我发现我可以通过使用“setProgress”和定义属性更改来完成这样的操作。

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);      
   }
}

我的问题是:我的第一个代码是否可接受,或者我应该出于任何原因使用setProgress?在我的情况下,我发现第二个代码更加复杂,不知道是否有任何优势或理由使用第二个。

有什么建议吗?

编辑 感谢回答。总的来说, 第一种解决方案“错误”,因为进度条更新是在EDT之外执行的。 第二个解决方案是“正确”的,因为进度条更新在EDT内执行

现在,根据@mKorbel的“有趣”回答,在我的情况下,我的计算结果以HTML文本形式呈现,我需要将其“插入”(请参见此链接)我的当前代码如下。

我发布(string),我的处理代码看起来像这样

@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) {
            }
        }
    }

我该如何借鉴 @mKobel 的方法,在我的情况下也实现相同的效果?我的意思是,他在我的情况下使用了覆盖表格渲染的方法,那么我应该覆盖哪个渲染器(jTextPane?)以及怎样做?


1
我认为你的第二种方法也是错误的。你正在使用一个监听器来设置值,这将调用监听器,然后设置值,然后再次调用监听器等等。我不确定这是否会真的发生,但感觉上就是错的。这样,你没有得到SwingWorker的好处,你仍在EDT中设置进度条的值。 - Martijn Courteaux
1
SwingWorker.publish(V...)方法是在doInBackground方法内部使用的,用于将中间结果传递到事件分派线程以在process方法中进行处理。 - Andrew Thompson
@Andrews Thompson:我使用“publish”来发布结果,但是在这里http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html中设置进度条值时,他们使用setProgress而不是publish,那么怎么办? - HpTerm
@MartijnCourteaux:我不确定是否理解。我使用了http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html来编写监听器。你能提供一些代码来解释你的意思吗? - HpTerm
1
@trashgod:哦,是的,我明白了。我没有注意到侦听器添加到了 SwingWorker 而不是 ProgressBar。谢谢!学到了点什么:D - Martijn Courteaux
4个回答

7
在第一段代码中,您在非EDT(事件调度线程)线程中调用了以下行。因此它不是线程安全的:
progressBar.setValue(value);

这可能导致意外行为,因为Swing不是设计为线程安全库。
有多种方法可以按照Swing的方式执行此操作。其中一种正确的方式是您在第二篇帖子中所做的。另一种方法是使用publish()/process()方法,第三种方法是编写自己的线程而不是SwingWorker并使用SwingUtilities.invokeLater()

你能否编辑你的回答,说一下你是指我的第二段代码是要使用的吗?因为评论中说它是“错误”的,但是根据这个http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html,我认为它是正确的。我应该使用“publish”来更新进度条吗?我认为“setprogress”是专门用来更新进度条的。 - HpTerm

5
你的第二种方法是正确的,甚至在SwingWorker类的文档中有记录。'progress'事件在EDT上触发,因此你的监听器在EDT上更新进度条。 这不是你第一种方法的情况。
另一种方法的示例(使用publish/process,如vizier所指示)可以在我之前在SO问题上的回答中找到

你提供的链接很有趣,我喜欢你在处理/发布过程中更新进度条的方式,而不是使用setProgress。我会仔细研究这个。 - HpTerm
1
Robin的有用示例展示了直接将工作者和进度条耦合的便利性;通过PropertyChangeListener松散耦合的优点是所有监听器都在EDT上被通知,而不仅仅是进度条。 - trashgod

5
我曾经使用进度条来监视长时间运行的任务,这个任务当然是在Swingworker线程中执行的。 现在你可以在所有情况下使用SwingWorker将任何繁重和长时间运行的任务重定向到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;
    }
}

但是为什么要使用SwingWorker来复杂化Wwing GUI呢?(这需要对Java Essential ClassesGenerics有深入的了解)

Runnable#Thread的基本实现只需要使用invokeLater来输出到Swing GUI中,而且如果是从EDT(从Swing/AWT Listener)启动,并且没有任何包含Thread.sleep(int)代码行,则只建议/要求在生产代码中使用invokeLater

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();
    }
}

对于真正的繁重且长时间运行的任务,你需要查看 Runnable#Thread(简单、易懂、无错误和清晰),只有当你非常熟悉JavaSwing时,才可以考虑使用SwingWorker


这个答案在我的情况下会生成异常,请参考问题 - Syed Muhammad Mubashir

4

正如这个示例所示,您在第二个示例中使用工作线程的setProgress()是正确的:任何PropertyChangeListener都会在事件分派线程上异步得到通知。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接