12

我用来通过更新 ProgressBar 来监控长时间运行的任务。长时间运行的任务当然是在 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 文本的结果(请参阅此链接)。我当前的代码如下。

我发布(字符串),我的流程代码看起来像这样

@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?)以及如何?

4

4 回答 4

7

在第一个代码中,您在非 EDT(事件调度程序线程)线程中调用以下行。所以它不是线程安全的:

progressBar.setValue(value);

这可能会导致意外行为,因为 Swing 并非设计为线程安全库。

有不同的方法可以以 Swing 方式执行此操作。一种正确的方法是您在第二篇文章中所做的。另一种方法是使用publish()/process()方法,第三种方法是编写自己的线程而不是SwingWorker使用SwingUtilities.invokeLater().

于 2012-05-27T11:19:34.200 回答
5

我用来通过更新 ProgressBar 来监控长时间运行的任务。长时间运行的任务当然是在 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;
    }
}

但是为什么要使用复杂的 Wwing GUI SwingWorker(也需要最深入的知识)Java Essential ClassesGenerics

Runnable#Thread仅需要输出到 Swing GUI 的基本实现invokeLater,并且在从 EDT(从 Swing/AWT 侦听器)开始的情况下,并且没有任何代码行包含Thread.sleep(int)theninvokeLater仅建议/需要生产代码

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(简单、容易、非错误和清晰的方式),只有你对Java&的了解Swing非常好,然后你才能考虑SwingWorker

于 2012-05-27T13:19:05.187 回答
4

您的第二种方法是正确的,甚至记录在该类的类 javadoc 中SwingWorker。'progress' 事件在 EDT 上触发,因此您的侦听器会更新 EDT 上的进度条。在您的第一种方法中不是这种情况。

publish/process可以在我对上一个 SO 问题的回答中找到另一种方法的示例(如 vizier 所示)

于 2012-05-27T11:59:07.957 回答
4

如本所示,您在第二个示例中对工作人员的使用setProgress()是正确的:任何PropertyChangeListener将在事件调度线程上异步通知。

于 2012-05-27T13:50:46.920 回答