简体   繁体   English

Swing Worker无法在模式Jdialog中更新GUI组件

[英]Swing Worker can't update GUI components in modal jdialog

I have a modal Jdialog with a progress bar and a text area. 我有一个带有进度条和文本区域的模式Jdialog。 I'm launching a Swing Worker to do some background tasks and update text area in jdialog using publish-process. 我正在启动Swing Worker,以执行一些后台任务,并使用发布过程更新jdialog中的文本区域。 However, on running the program I'm observing that even though process method is called, it's still not updating the text area in jdialog. 但是,在运行程序时,我观察到即使调用了process方法,它仍然不会更新jdialog中的文本区域。 Also, note that I'm closing the jdialog in done method of swing worker and it works fine. 另外,请注意,我将在swing worker的done方法中关闭jdialog,它工作正常。 Can anyone tell me why gui updates from (SwingWorker's) process method are not happening? 谁能告诉我为什么(SwingWorker)处理方法中的gui更新没有发生?

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

}

} }

Controller code that calls Jdialog and Swingworker - 调用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);

I read somewhere that once modal Jdialog is made visible EDT is blocked till it's closed again? 我在某处读到,一旦使模式Jdialog可见,EDT就会被阻止直到再次关闭? But that still doesn't explain why done method in SwingWorker is able to close Jdialog. 但这仍然不能解释为什么SwingWorker中的done方法能够关闭Jdialog。

Edit: I know I am calling process method only once (it's supposed to be in a while loop). 编辑:我知道我只调用一次处理方法(应该在while循环中)。 I intend to use publish-process mechanism for more time consuming tasks after I can get this scenario (gui updates from process method) working. 在使这种情况(从处理方法中进行gui更新)生效之后,我打算将发布过程机制用于更多耗时的任务。

Also note that sleep in done method is used only for testing. 另请注意,“完成睡眠”方法仅用于测试。 I can reproduce the same issue without using sleep method in done method. 我可以在完成方法中不使用睡眠方法来重现相同的问题。

One huge problem is right here: 一个巨大的问题就在这里:

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

The worker's done() method is called on the EDT or event dispatch thread, and you know what happens when you sleep this thread -- the entire GUI goes to sleep and becomes completely unresponsive. 在EDT或事件分派线程上调用了worker的done()方法,您知道该线程处于休眠状态时会发生什么-整个GUI进入休眠状态,并且变得完全无响应。 If you want to delay the disposal of the dialog and keep your GUI functioning, don't do this, and in fact never call Thread.sleep on the EDT. 如果要延迟处理对话框并保持GUI正常运行,请不要这样做,并且实际上不要在EDT上调用Thread.sleep

Option 1: 选项1:

Instead is in most all other Swing delay tactics, use a Swing Timer, for example, something like this (code not tested): 相反,在大多数其他所有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();
}

If you're not familiar with use of Swing Timers, then please check out the Swing Timer Tutorial 如果您不熟悉Swing计时器的使用,请查看Swing计时器教程

Option 2: 选项2:

Put your Thread.sleep(5000) at the end of your doInBackground() method, right before return null; Thread.sleep(5000)放在doInBackground()方法的末尾,就在return null;之前return null;


Another problem: you're using the publish/process method pair but only once which defeats its purpose. 另一个问题:您使用的是publish / process方法对,但是只有一次 ,这违反了它的目的。 Perhaps you meant to call publish within the polling while loop? 也许您打算在轮询while循环内调用发布? Of course you can only call get() once, but is there another way to extract information from the running process that you're calling? 当然,您只能调用一次get() ,但是还有另一种方法可以从正在运行的进程中提取信息吗? If so, then the information needs to be obtained intermittently, either within the polling while loop, or by whatever process allows you to extract interim results. 如果是这样,则需要间歇地获取信息,或者在轮询while循环内,或者通过任何允许您提取中期结果的过程。

For example: 例如:

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

}

The problem here is your Jdialog box is modal. 这里的问题是您的Jdialog框是模态的。 Whenever the popup(or JDialog) is setModal(true) the EDT thread is blocked. 每当popup(或JDialog)设置为setModal(true) ,EDT线程就会被阻止。 That's how user will not be able to do any operation when popup with modal true . 这样,当使用模式true弹出时,用户将无法执行任何操作。

You have to make it setmodal(false) and then update contents in popup and thereafter again make it setmodal(true) . 您必须将其设置为setmodal(false) ,然后更新弹出窗口中的内容,然后再次将其设置为setmodal(true)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM