java - Swing Worker 无法更新模态 jdialog 中的 GUI 组件

标签 java multithreading swing swingworker

我有一个带有进度条和文本区域的模态 Jdialog。我正在启动一个 Swing Worker 来执行一些后台任务并使用发布过程更新 jdialog 中的文本区域。然而,在运行程序时,我观察到即使调用了 process 方法,它仍然没有更新 jdialog 中的文本区域。另外,请注意,我正在 Swing Worker 的完成方法中关闭 jdialog,它工作正常。谁能告诉我为什么(SwingWorker)流程方法的 gui 更新没有发生?

JDialog 类 -

public class ProgressDialog extends JDialog {

private static final long serialVersionUID = 1L;
private GridBagLayout gridBag;
private GridBagConstraints constraints;
private ProgressDialog.ProgressBar progressBar;
private JScrollPane scrollPane;
private JTextArea textArea;

ProgressDialog(Frame owner, String title, boolean modal, int numTasks) {

    super(owner,title,modal);

    gridBag = new GridBagLayout();
    constraints = new GridBagConstraints();

    this.progressBar = new ProgressDialog.ProgressBar();
    this.progressBar.init(numTasks);

    this.textArea = new JTextArea(10,30);
    this.textArea.setEditable(false);
    this.scrollPane = new JScrollPane(this.textArea);
    this.scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    this.scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);

    this.setLayout(gridBag);

    constraints.gridx = 0;
    constraints.gridy = 0;
    gridBag.setConstraints(progressBar, constraints);

    constraints.gridx = 0;
    constraints.gridy = 1;
    constraints.insets = new Insets(10,0,10,0);
    gridBag.setConstraints(scrollPane, constraints);


    this.add(progressBar);
    this.add(scrollPane);
    this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    this.setSize(400, 300);
    this.setLocationRelativeTo(null);
}

class ProgressBar extends JProgressBar {

    ProgressBar() {

    }

    void init(int numTasks) {
        setMaximum(numTasks);
        setStringPainted(true);
        setValue(0);
    }

    void setProgress(int taskCompleted) {
        int totalTasks = getMaximum();
        setValue(taskCompleted);
        setString(taskCompleted+"/"+totalTasks);
    }
}

static long getSerialversionuid() {
    return serialVersionUID;
}

GridBagLayout getGridBag() {
    return gridBag;
}

GridBagConstraints getConstraints() {
    return constraints;
}

ProgressDialog.ProgressBar getProgressBar() {
    return progressBar;
}

JScrollPane getScrollPane() {
    return scrollPane;
}

JTextArea getTextArea() {
    return textArea;
}

}

SwingWorker 类:

public class SwingWorkers {

static class ConnPool extends SwingWorker<Void, TaskType<String>>{

    private ProgressDialog pDialog;

    ConnPool(ProgressDialog pDialog) {
        this.pDialog = pDialog;
    }

    protected Void doInBackground() throws Exception {
        Runner<String> runner = new Runner<>();

        Future<TaskType<String>> fut = runner.run(new Tasks.InitResources());
        runner.shutDown();

        while(!fut.isDone()) {
            Thread.sleep(1000);
        }

        publish(fut.get());

        return null;
    }

    protected void process(List<TaskType<String>> results) {
        if(results.size() > 0) {
            TaskType<String> lastResult = results.get(results.size()-1);
            pDialog.getTextArea().append(lastResult.getTaskStatusMesg());
            pDialog.getTextArea().append("\n");
            pDialog.getProgressBar().setValue(results.size());
        }
    }

    protected void done() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        pDialog.dispose();
    }

}

}

调用 Jdialog 和 Swingworker 的 Controller 代码 -

JFrame parent = GUI.getInstance().getFrame();
        ProgressDialog pDialog = new ProgressDialog(parent,"Working...",false,1);
        pDialog.getTextArea().append("Initializing background tasks");
        new SwingWorkers.ConnPool(pDialog).execute();
        pDialog.setVisible(true);

我在某处读到,一旦模态 Jdialog 可见,EDT 就会被阻止,直到再次关闭为止?但这仍然不能解释为什么SwingWorker中的done方法能够关闭Jdialog。

编辑: 我知道我只调用一次 process 方法(它应该在 while 循环中)。在我可以让这个场景(来自流程方法的 GUI 更新)工作之后,我打算使用发布流程机制来执行更耗时的任务。

另请注意,sleep in done 方法仅用于测试。我可以在 Done 方法中不使用 sleep 方法的情况下重现相同的问题。

最佳答案

这里有一个大问题:

protected void done() {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    pDialog.dispose();
}

worker 的done()方法在 EDT 或事件调度线程上调用,并且您知道当您 sleep 该线程时会发生什么 - 整个 GUI 进入休眠状态并变得完全无响应。如果您想延迟对话框的处理并保持 GUI 正常运行,请不要这样做,事实上永远调用 Thread.sleep在美国东部时间。

选项 1:

相反,在大多数其他 Swing 延迟策略中,请使用 Swing 计时器,例如,如下所示(代码未测试):

protected void done() {
    int timerDelay = 5000;
    new Timer(timerDelay, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            pDialog.dispose();
            ((Timer) e.getSource).stop();
        }
    }).start();
}

如果您不熟悉 Swing Timers 的使用,请查看Swing Timer Tutorial

选项 2:

输入您的Thread.sleep(5000)在你的 doInBackground() 的末尾方法,就在 return null; 之前

<小时/>

另一个问题:您正在使用发布/处理方法对,但仅使用一次,这违背了其目的。也许您打算在轮询 while 循环中调用发布?当然你只能调用get()一次,但是还有其他方法可以从您正在调用的正在运行的进程中提取信息吗?如果是这样,则需要间歇性地获取信息,无论是在轮询 while 循环中,还是通过允许您提取临时结果的任何过程。

例如:

import java.awt.BorderLayout;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import javax.swing.*;


public class TestProgressDialog extends JPanel {
    private static final long serialVersionUID = 1L;
    private ProgressDialog pDialog;
    private JSpinner taskNumberSpinner = new JSpinner(new SpinnerNumberModel(10, 1, 20, 1));

    public TestProgressDialog() {
        setPreferredSize(new Dimension(800, 650));
        add(new JButton(new AbstractAction("Launch Dialog") {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                Window owner = SwingUtilities.windowForComponent(TestProgressDialog.this);
                String title = "Dialog";
                ModalityType modal = ModalityType.MODELESS;
                int numTasks = (int) taskNumberSpinner.getValue();
                pDialog = new ProgressDialog(owner, title, modal, numTasks);
                pDialog.pack();
                pDialog.setLocationByPlatform(true);
                pDialog.append("Initializing background tasks\n");
                MyWorker myWorker = new MyWorker(numTasks, pDialog);
                myWorker.addPropertyChangeListener(new WorkerListener(pDialog));
                myWorker.execute();
                pDialog.setVisible(true);
            }
        }));
        add(new JLabel("Number of Tasks:"));
        add(taskNumberSpinner);
    }

    private static void createAndShowGui() {
        TestProgressDialog mainPanel = new TestProgressDialog();

        JFrame frame = new JFrame("Test Progress Dialog");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

interface Progressable {
    void setProgress(int progress);
    void append(String text);
}

class ProgressDialog extends JDialog implements Progressable {
    private static final long serialVersionUID = 1L;
    private JProgressBar progressBar = new JProgressBar(0, 100);
    private JTextArea textArea = new JTextArea(10, 30);
    private JScrollPane scrollPane = new JScrollPane(textArea);

    ProgressDialog(Window owner, String title, ModalityType modal, int nunTasks) {
        super(owner, title, modal);
        progressBar.setStringPainted(true);

        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        add(progressBar, BorderLayout.PAGE_START);
        add(scrollPane);
    }

    @Override
    public void append(String text) {
        textArea.append(text);
    }

    @Override
    public void setProgress(int progress) {
        progressBar.setValue(progress);
    }

}

class MyCallable implements Callable<String> {
    private static final long MAX_DUR = 6 * 1000;
    private static final long MIN_DUR = 1000;
    private String text;

    public MyCallable(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    @Override
    public String call() throws Exception {
        // just wait some random delay and then return a String
        long timeout = (long) (Math.random() * MAX_DUR + MIN_DUR);
        TimeUnit.MILLISECONDS.sleep(timeout);
        return text + " time out: " + timeout;
    }

}

class WorkerListener implements PropertyChangeListener {
    private Progressable progressable;

    public WorkerListener(Progressable progressable) {
        this.progressable = progressable;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName() != null && evt.getPropertyName().equals("progress")) { 
            int progress = (int)evt.getNewValue();
            progressable.setProgress(progress);
        }
    }
}

class MyWorker extends SwingWorker<Void, String> {

    private Progressable progressable;
    private List<Future<String>> futures = new ArrayList<>();
    private CompletionService<String> completionService;
    private int numTasks;
    // private BlockingQueue<Future<String>> completionQueue;


    public MyWorker(int numTasks, Progressable progressable) {
        this.numTasks = numTasks;
        this.progressable = progressable;
        ExecutorService service = Executors.newFixedThreadPool(numTasks);
        completionService = new ExecutorCompletionService<>(service);

        for (int i = 0; i < numTasks; i++) {
            futures.add(completionService.submit(new MyCallable("My Callable " + i)));
        }
        service.shutdown();
    }

    @Override
    protected Void doInBackground() throws Exception {
        while (futures.size() > 0) {
            Future<String> future = completionService.take();
            futures.remove(future);
            int progress = (100 * (numTasks - futures.size())) / numTasks;
            progress = Math.min(100, progress);
            progress = Math.max(0, progress);
            setProgress(progress);
            if (future != null) {
                publish(future.get());
            }
        }
        return null;
    }

    @Override
    protected void process(List<String> chunks) {
        for (String chunk : chunks) {
            progressable.append(chunk + "\n");
        }
    }

    public Progressable getpDialog() {
        return progressable;
    }

}

关于java - Swing Worker 无法更新模态 jdialog 中的 GUI 组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54496606/

相关文章:

java - SQLite 多个连接。 java

java - 如何使用 Java 在 JPanel 中显示 JTable?

java - Python boolean 语句

java - 使用反射实例化内部类时出现 InstantiationException。为什么?

c - 在核心之间共享数据的最有效方式

java - 在指定目录启动 JFileChooser 并仅显示特定类型的文件

java - 如何使用 Substance LaF 获取按钮的文本颜色?

java - 为什么语言设计者允许界面包含字段?

java - 分成 double 不起作用

c# - C# 的空条件委托(delegate)调用线程安全吗?