SwingWorker无法从进程内部更新进度条

问题描述 投票:-4回答:1

我有一个Java 8 Swing应用程序,当用户单击一个新按钮时,需要向它添加一个耗时的操作。我认为这是SwingWorker的完美用例,尽管我以前从未写过。完整的源代码和reproducible Swing app is here

[当用户单击按钮时,应用程序必须从几个不同的来源收集信息,然后启动此后台操作。它将计算一个InputAnalysis,然后将该InputAnalysis返回给EDT中的单击处理程序以更新UI。在运行时,我也希望它也更新JProgressBar,以便用户看到正在取得的进展。迄今为止我最大的尝试:

我的[[InputAnalysis POJO:

package com.example.swingworker; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @Getter @Setter @AllArgsConstructor public class InputAnalysis { private Boolean isFizz; private String superSecretAnswer; private Integer numDingers; }
我的

SimpleApp

Swing / GUI应用程序,包括EDT代码:package com.example.swingworker; import com.jeta.forms.components.panel.FormPanel; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; @Slf4j public class SimpleApp { public static void main(String[] args) { SimpleApp app = new SimpleApp(); app.run(); } public void run() { SwingUtilities.invokeLater(() -> { log.info("starting app"); JFrame.setDefaultLookAndFeelDecorated(true); JFrame mainWindow = new JFrame("Some Simple App!"); mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWindow.setResizable(true); mainWindow.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { log.warn("app is shutting down"); System.exit(0); } }); FormPanel panel = new FormPanel("simpleAppForm.jfrm"); JTextField superSecretInfoTextField = panel.getTextField("superSecretInfoTextField"); JButton analyzeButton = (JButton) panel.getButton("analyzeButton"); JProgressBar progressBar = panel.getProgressBar("progressBar"); progressBar.setValue(0); AnalysisService analysisService = new AnalysisService(); analyzeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // on click, scoop some info from the input and run a time-consuming task. // usually takes 20 - 30 seconds to run, and I'd like to be updating the progress // bar during that time. // // also need to handle cases where the task encounters a POSSIBLE error and needs to // communicate back to the EDT to display a JOPtionPane to the user; and then get the // user's response back and handle it. // // also need to handle the case where the long running task encounters both a checked // and unchecked/unexpected exception String superSecretInfo = superSecretInfoTextField.getText(); // here is where we start the long-running task. ideally this needs to go into a SwingWorker // however there is a somewhat complex back-and-forth-communication required. see the analysis // method comments for details try { InputAnalysis analysis = analysisService.analyze(superSecretInfo, mainWindow, progressBar); superSecretInfoTextField.setText(analysis.getSuperSecretAnswer()); } catch (IOException ex) { log.error(ExceptionUtils.getStackTrace(ex)); JOptionPane.showMessageDialog( mainWindow, "Something went wrong", "Aborted!", JOptionPane.WARNING_MESSAGE); } // comment the above try-catch out and uncomment all the worker code below to switch over // to the async/non-blocking worker based method // AnalysisWorker analysisWorker = new AnalysisWorker(mainWindow, progressBar, superSecretInfo); // analysisWorker.addPropertyChangeListener(evt -> { // // if (evt.getNewValue() == SwingWorker.StateValue.DONE) { // try { // // this is called on the EDT // InputAnalysis asyncAnalysis = analysisWorker.get(); // superSecretInfoTextField.setText(asyncAnalysis.getSuperSecretAnswer()); // // } catch (Exception ex) { // log.error(ExceptionUtils.getStackTrace(ex)); // } // } // // }); // // analysisWorker.execute(); } }); mainWindow.add(panel); mainWindow.pack(); mainWindow.setLocationRelativeTo(null); mainWindow.setVisible(true); log.info("application started"); }); } }
我的[[AnalysisService
,当前阻止EDT并阻止进度条在屏幕上显示:

package com.example.swingworker; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import javax.swing.*; import java.io.IOException; /** * This class is where the long-running task currently lives, and because of how its * coded, totally blocks the EDT and prevents the progress bar from updating. * * It should be replaced by or somehow itself leverage/invoke a SwingWorker, specifically * the {@link AnalysisWorker} that I've already started. */ @Slf4j public class AnalysisService { public InputAnalysis analyze( String superSecretInfo, JFrame mainWindow, JProgressBar progressBar) throws IOException { progressBar.setValue(25); // simulate a few seconds of processing try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { log.error(ExceptionUtils.getStackTrace(e)); throw new RuntimeException("SOMETHIN BLEW UP"); } // now we are ready to analyze the input which itself can take 10 - 15 seconds but // we'll mock it up here if (superSecretInfo == null || superSecretInfo.isEmpty()) { // if the input is null/empty, we'll consider that a "checked exception"; something the // REAL code I'm using explicitly has a try-catch for because the libraries I'm using throw // them throw new IOException("ERMERGERD"); } else if (superSecretInfo.equals("WELL_WELL_WELL")) { // here we'll consider this an unchecked exception throw new RuntimeException("DID NOT SEE THIS ONE COMING"); } progressBar.setValue(55); // check to see if the input equals "KEY MASTER"; if it does we need to go back to the EDT // and prompt the user with a JOptionPane if (superSecretInfo.equalsIgnoreCase("KEY MASTER")) { int answer = JOptionPane.showConfirmDialog( mainWindow, "We have identified a KEY MASTER scenario. Do you wish to proceed?", "Do you wish to proceed", JOptionPane.YES_NO_OPTION); if (answer == JOptionPane.NO_OPTION) { // return a partial InputAnalysis and return Boolean isFizz = Boolean.TRUE; String superSecretAnswer = "HERE IS A PARTIAL ANSWER"; Integer numDingers = 5; return new InputAnalysis(isFizz, superSecretAnswer, numDingers); } } // if we get here, either KEY MASTER was not in the input or they chose to proceed anyway Boolean isFizz = superSecretInfo.length() < 5 ? Boolean.TRUE : Boolean.FALSE; String superSecretAnswer = "HERE IS A FULL ANSWER"; Integer numDingers = 15; progressBar.setValue(100); return new InputAnalysis(isFizz, superSecretAnswer, numDingers); } } 最后,我正在尝试的

AnalysisWorker
。做与AnalysisService完全相同的事情,但是

应该以非阻塞方式做同样的事情:

package com.example.swingworker; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import javax.swing.*; import java.io.IOException; import java.util.List; @Getter @Setter @AllArgsConstructor @Slf4j public class AnalysisWorker extends SwingWorker<InputAnalysis,Integer> { private JFrame mainWindow; private JProgressBar progressBar; private String superSecretInfo; @Override protected void process(List<Integer> chunks) { progressBar.setValue(chunks.size() - 1); } @Override protected InputAnalysis doInBackground() throws Exception { publish(25); // simulate a few seconds of processing try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { log.error(ExceptionUtils.getStackTrace(e)); throw new RuntimeException("SOMETHIN BLEW UP"); } // now we are ready to analyze the input which itself can take 10 - 15 seconds but // we'll mock it up here if (superSecretInfo == null || superSecretInfo.isEmpty()) { // if the input is null/empty, we'll consider that a "checked exception"; something the // REAL code I'm using explicitly has a try-catch for because the libraries I'm using throw // them throw new IOException("ERMERGERD"); } else if (superSecretInfo.equals("WELL_WELL_WELL")) { // here we'll consider this an unchecked exception throw new RuntimeException("DID NOT SEE THIS ONE COMING"); } publish(55); // check to see if the input equals "KEY MASTER"; if it does we need to go back to the EDT // and prompt the user with a JOptionPane if (superSecretInfo.equalsIgnoreCase("KEY MASTER")) { int answer = JOptionPane.showConfirmDialog( mainWindow, "We have identified a KEY MASTER scenario. Do you wish to proceed?", "Do you wish to proceed", JOptionPane.YES_NO_OPTION); if (answer == JOptionPane.NO_OPTION) { // return a partial InputAnalysis and return Boolean isFizz = Boolean.TRUE; String superSecretAnswer = "HERE IS A PARTIAL ANSWER"; Integer numDingers = 5; return new InputAnalysis(isFizz, superSecretAnswer, numDingers); } } // if we get here, either KEY MASTER was not in the input or they chose to proceed anyway Boolean isFizz = superSecretInfo.length() < 5 ? Boolean.TRUE : Boolean.FALSE; String superSecretAnswer = "HERE IS A FULL ANSWER"; Integer numDingers = 15; publish(100); return new InputAnalysis(isFizz, superSecretAnswer, numDingers); } } [当我注释掉容纳try-catchAnalysisService块,并取消注释AnalysisWorker的代码时,进度栏仍未正确更新。
不是必需的,因为上面提供了SSCCE的所有必需代码,但是如果您

有兴趣

快速构建和运行此代码,我已经在GitHub上准备了此SimpleApp repo,以节省您一些时间。但是,不必回答这个问题,上面提供了所有代码。 100%。
java swing swingworker edt
1个回答
1
投票
progressBar.setValue(chunks.size() - 1);

由于集合的大小不是您想要的。相反,您希望显示集合的值:

for (int item : chucks) {
    progressBar.setValue(item);
}

您还需要在其工作人员完成工作时在其get()方法中或在该工作人员的状态值为done()时通知您的PropertyChangeListener中调用SwingWorker.StateValue.DONE

相同的get()调用将在EDT上返回新的InputAnalysis对象,如果在工作程序运行期间发生任何异常,则将引发异常,因此您可以在那里处理它们。

例如,

analyzeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Fizz fizz = fizzService.fetchFromWs(1234); // make this guy final final Analyzer analyzer = new Analyzer(progressBar, nameTextField.getText(), fizz); analyzer.addPropertyChangeListener(evt -> { // this is a call-back method and will be called in response to // state changes in the SwingWorker if (evt.getNewValue() == SwingWorker.StateValue.DONE) { try { // this is called on the EDT InputAnalysis analysis = analyzer.get(); // do what you want with it here } catch (Exception e) { e.printStackTrace(); } } }); analyzer.execute(); // but now, how do I obtain the InputAnalysis instance?! // InputAnalysis analysis = null; // analyzer.getSomehow(); } }

((未经测试的代码)

附带说明:您可以通过简单地将worker的进度绑定字段更改为0到100之间的任何值来取消发布/处理方法对。然后在同一PropertyChangeListener中侦听并响应此属性中的更改。

例如,使用框架InputAnalysis类对代码进行排序:


import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import javax.swing.*; public class SwingWorkerExample { private static void createAndShowGui() { SwGui mainPanel = new SwGui(); JFrame frame = new JFrame("SwingWorker Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(mainPanel); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> createAndShowGui()); } }

@SuppressWarnings("serial") class SwGui extends JPanel { private JProgressBar progressBar = new JProgressBar(0, 100); private JTextArea textArea = new JTextArea(14, 40); private StartAction startAction = new StartAction("Start", this); public SwGui() { JPanel bottomPanel = new JPanel(); bottomPanel.add(new JButton(startAction)); progressBar.setStringPainted(true); textArea.setFocusable(false); JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); setLayout(new BorderLayout()); add(progressBar, BorderLayout.PAGE_START); add(scrollPane); add(bottomPanel, BorderLayout.PAGE_END); } public void appendText(String text) { textArea.append(text + "\n"); } public void setProgressValue(int value) { progressBar.setValue(value); } }

@SuppressWarnings("serial")
class StartAction extends AbstractAction {
    private SwGui gui;
    private AnalyzerWorker worker;
    private InputAnalysis inputAnalysis;

    public StartAction(String text, SwGui gui) {
        super(text);
        this.gui = gui;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        worker = new AnalyzerWorker();
        setEnabled(false); // turn off button
        gui.appendText("START");
        worker.addPropertyChangeListener(evt -> {
            if (evt.getPropertyName().equals("progress")) {
                int progress = (int) evt.getNewValue();
                gui.setProgressValue(progress);
                gui.appendText(String.format("Percent done: %03d%%", progress));
            } else if (evt.getPropertyName().equals("state")) {
                if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                    setEnabled(true);
                    try {
                        inputAnalysis = worker.get();
                        String analysisText = inputAnalysis.getText();
                        gui.appendText(analysisText);
                    } catch (InterruptedException | ExecutionException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        });
        worker.execute();
    }
}
class InputAnalysis {

    public String getText() {
        return "DONE";
    }

}
class AnalyzerWorker extends SwingWorker<InputAnalysis, Void> {
    private static final int MAX_VALUE = 100;

    @Override
    protected InputAnalysis doInBackground() throws Exception {
        int value = 0;
        setProgress(value);
        while (value < MAX_VALUE) {
            // create random values up to 100 and sleep for random time
            TimeUnit.SECONDS.sleep((long) (2 * Math.random()));
            value += (int) (8 * Math.random());
            value = Math.min(MAX_VALUE, value);
            setProgress(value);
        }

        return new InputAnalysis();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.