【问题标题】:Swing Worker can't update GUI components in modal jdialogSwing Worker 无法更新模态 jdialog 中的 GUI 组件
【发布时间】:2019-02-02 19:13:53
【问题描述】:

我有一个带有进度条和文本区域的模态 Jdialog。我正在启动一个 Swing Worker 来执行一些后台任务并使用发布过程更新 jdialog 中的文本区域。但是,在运行程序时,我观察到即使调用了 process 方法,它仍然没有更新 jdialog 中的文本区域。另外,请注意,我在 swing worker 的 done 方法中关闭了 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 的控制器代码 -

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 方法来重现相同的问题。

【问题讨论】:

    标签: java multithreading swing swingworker


    【解决方案1】:

    这里有个大问题:

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

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

    选项 1:

    在大多数其他 Swing 延迟策略中,使用 Swing Timer,例如,类似这样的东西(代码未经测试):

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

    【讨论】:

    • 是的。我补充说仅用于测试(以确保 jdialog 不会立即消失)。我打算在从进程方法工作中获取 gui 更新后删除睡眠。而且它仍然没有解释为什么 GUI 更新没有从 process 方法发生。无论如何都会在完成之前调用它(我使用 sysout 消息验证了它)
    • 是的,但它仍然不能解释过程方法中的 gui 更新不起作用。还是我在这里遗漏了什么?
    • @Archit:我很好奇您为什么使用工作人员的发布/处理方法对,但只在 while 循环之后使用一次,而不是在轮询 while 循环内。这是一种奇怪的接线方式。
    • @Archit:请参阅附录以回答提到这一点。
    • @Archit:当然,您只能在 Future 任务上调用一次get(),并且只能在它完成所有操作时调用。您是否计划在此 SwingWorker 中运行 多个 任务?如果是这样,中期结果会是每项任务完成时的结果吗?如果是这样,那么您不想使用轮询,而是使用其他方式在已完成工作的集合中捕获 Future,在其上调用 get() 并在发布/进程中更新 GUI。
    【解决方案2】:

    这里的问题是您的 Jdialog 框是模态的。每当弹出窗口(或 JDialog)为setModal(true) 时,EDT 线程就会被阻塞。这就是当弹出模式true 时用户将无法执行任何操作的方式。

    您必须将其设为setmodal(false),然后更新弹出窗口中的内容,然后再次将其设为setmodal(true)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-23
      • 1970-01-01
      • 1970-01-01
      • 2010-10-15
      相关资源
      最近更新 更多