Swing Worker не может обновить компоненты графического интерфейса в модальном jdialog - PullRequest
0 голосов
/ 02 февраля 2019

У меня есть модальный Jdialog с индикатором выполнения и текстовой областью.Я запускаю Swing Worker для выполнения некоторых фоновых задач и обновления текстовой области в jdialog с помощью процесса публикации.Тем не менее, при запуске программы я наблюдаю, что хотя метод процесса вызывается, он все равно не обновляет текстовую область в jdialog.Также обратите внимание, что я закрываю jdialog в готовом методе Swing worker, и он работает нормально.Может кто-нибудь сказать мне, почему обновления графического интерфейса из метода процесса (SwingWorker's) не происходят?

Класс 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 блокируется, пока он снова не закроется?Но это все еще не объясняет, почему метод done в SwingWorker может закрыть Jdialog.

Редактировать: Я знаю, что вызываю метод процесса только один раз (он должен быть в цикле while).Я намереваюсь использовать механизм публикации-процесса для более трудоемких задач после того, как я смогу заставить этот сценарий (обновления графического интерфейса из метода процесса) работать.

Также обратите внимание, что метод sleep in done используется только для тестирования.Я могу воспроизвести ту же проблему, не используя метод сна в методе done.

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

Проблема здесь в том, что ваша коробка Jdialog является модальной.Всякий раз, когда всплывающее окно (или JDialog) равно setModal(true), поток EDT блокируется.Таким образом, пользователь не сможет выполнять какие-либо операции при всплывающем окне с модальным true.

Вы должны сделать это setmodal(false), а затем обновить содержимое во всплывающем окне и затем снова сделать его setmodal(true).

0 голосов
/ 02 февраля 2019

Здесь есть одна огромная проблема:

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

Метод done() рабочего вызывается в потоке EDT или в потоке диспетчеризации событий, и вы знаете, что происходит, когда вы спите в этом потоке - весь GUIидет спать и становится совершенно не отвечает.Если вы хотите отложить удаление диалогового окна и сохранить работу графического интерфейса, не делайте этого, и на самом деле никогда вызовите Thread.sleep на EDT.

Вариант 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, ознакомьтесь с Учебником по Swing Timer

Вариант 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;
    }

}
...