简体   繁体   English

SwingWorker 未从进程内部更新进度条

[英]SwingWorker not updating progressbar from inside process

I have a Java 8 Swing app and need to add a time-consuming operation to it when the user clicks a new button.我有一个 Java 8 Swing 应用程序,需要在用户单击新按钮时为其添加一个耗时的操作。 I think its a perfect use case for a SwingWorker although I've never written one before.我认为它是SwingWorker的完美用例,尽管我以前从未写过。 The full source code and reproducible Swing app is here .完整的源代码和可重现的 Swing 应用程序在这里

When the user clicks a button, the app must collect information from a few different sources and then start this background operation.当用户点击一个按钮时,应用程序必须从几个不同的来源收集信息,然后启动这个后台操作。 It will compute an InputAnalysis and then return that InputAnalysis back to the click handler in the EDT to update the UI.它将计算InputAnalysis ,然后将该InputAnalysis返回到 EDT 中的单击处理程序以更新 UI。 While it works I want it to update a JProgressBar as well so the user sees progress being made.虽然它可以工作,但我希望它也更新JProgressBar ,以便用户看到正在取得的进展。 My best attempt thus far:迄今为止我最好的尝试:

package com.example.swingworker.suchwow;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.List;

public class MegaApp {

    public static void main(String[] args) {
        new MegaApp().run();
    }

    public void run() {

        SwingUtilities.invokeLater(() -> {

            System.out.println("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) {
                System.out.println("app is shutting down");
                System.exit(0);
                }
            });


            JPanel jPanel = new JPanel();

            JTextField superSecretInfoTextField = new JTextField();
            JButton analyzeButton = new JButton("Analyze");
            JProgressBar progressBar = new JProgressBar();

            superSecretInfoTextField.setPreferredSize(new Dimension(200,
                (int)superSecretInfoTextField.getPreferredSize().getHeight()));

            jPanel.add(superSecretInfoTextField);
            jPanel.add(analyzeButton);
            jPanel.add(progressBar);

            progressBar.setValue(0);

            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(progressBar, superSecretInfo, mainWindow);
                        superSecretInfoTextField.setText(analysis.getSuperSecretAnswer());

                    } catch (IOException ex) {
                        System.out.println(ex.getMessage());
                        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

//                    MegaWorker analysisWorker = new MegaWorker(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) {
//                                System.out.println(ex.getMessage());
//                            }
//                        }
//
//                    });
//
//                    analysisWorker.execute();


                }
            });

            mainWindow.add(jPanel);
            mainWindow.pack();
            mainWindow.setLocationRelativeTo(null);
            mainWindow.setVisible(true);

            System.out.println("application started");

        });

    }

    public InputAnalysis analysisService_analyze(JProgressBar progressBar,
                                                 String superSecretInfo,
                                                 JFrame mainWindow) throws IOException {
        progressBar.setValue(25);

        // simulate a few seconds of processing
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
            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);

    }

    public class InputAnalysis {

        private Boolean isFizz;
        private String superSecretAnswer;
        private Integer numDingers;

        public InputAnalysis(Boolean isFizz, String superSecretAnswer, Integer numDingers) {
            this.isFizz = isFizz;
            this.superSecretAnswer = superSecretAnswer;
            this.numDingers = numDingers;
        }

        public Boolean getFizz() {
            return isFizz;
        }

        public void setFizz(Boolean fizz) {
            isFizz = fizz;
        }

        public String getSuperSecretAnswer() {
            return superSecretAnswer;
        }

        public void setSuperSecretAnswer(String superSecretAnswer) {
            this.superSecretAnswer = superSecretAnswer;
        }

        public Integer getNumDingers() {
            return numDingers;
        }

        public void setNumDingers(Integer numDingers) {
            this.numDingers = numDingers;
        }
    }

    public class MegaWorker extends SwingWorker<InputAnalysis,Integer> {

        private JFrame mainWindow;
        private JProgressBar progressBar;
        private String superSecretInfo;

        public MegaWorker(JFrame mainWindow, JProgressBar progressBar, String superSecretInfo) {
            this.mainWindow = mainWindow;
            this.progressBar = progressBar;
            this.superSecretInfo = 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) {
                System.out.println(e.getMessage());
                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);

        }

    }

}

When I comment-out the try-catch block that houses my analysisService_analyze() call, and uncomment the code for my MegaWorker , the progress bar is still not updated properly.当我注释掉包含我的analysisService_analyze()调用的try-catch块并取消注释我的MegaWorker的代码时,进度条仍然没有正确更新。

Not required because all of the necessary code for an SSCCE is provided above, but if you are interested in building and running this code quickly I have prepared this SimpleApp repo on GitHub to save you some time.不需要,因为上面提供了SSCCE 的所有必要代码,但如果您有兴趣快速构建和运行此代码,我已经在 GitHub 上准备了这个SimpleApp 存储库,以节省您的时间。 But not necessary for answering this question, again, all of the code is provided above.但是对于回答这个问题不是必需的,同样,上面提供了所有代码。 100%. 100%。

Not不是

progressBar.setValue(chunks.size() - 1);

Since the size of the collection is not what you're after.由于集合的大小不是您所追求的。 Rather you want the value of the collection to be shown:相反,您希望显示集合的值:

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

You also need to call get() on your worker when it has completed its work, either in its done() method or in a PropertyChangeListener that notifies you when the worker's state value is SwingWorker.StateValue.DONE您还需要在您的工作人员完成其工作后调用get() ,无论是在其done()方法中还是在当工作人员的状态值为SwingWorker.StateValue.DONE时通知您的 PropertyChangeListener 中

That same get() call will return the new InputAnalysis object on the EDT and will throw exceptions if any occur during the worker's run, so you can handle them there.同样的get()调用将在 EDT 上返回新的InputAnalysis对象,如果在工作InputAnalysis运行期间发生任何异常,将抛出异常,因此您可以在那里处理它们。

eg,例如,

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

(code not tested) (代码未测试)

Side note: you can do away with the publish/process method pair by simply changing the worker's progress bound field to any value between 0 and 100. Then in the same PropertyChangeListener listen and respond to changes in this property.旁注:您可以通过简单地将工作进程的进度绑定字段更改为 0 到 100 之间的任何值来取消发布/处理方法对。然后在同一个 PropertyChangeListener 中侦听并响应此属性中的更改。


For example, sort-of your code, using a skeleton InputAnalysis class:例如,使用骨架 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();
    }
}

Regarding changes to your question and your code:关于您的问题和代码的更改:

You look to need a background process that can hold a 2-way "conversation" with the GUI, and to do this, best way I can think of is to create a state machine for your background process, notifying the GUI when the background process changes state, via a property change listener, and allowing the GUI to respond to this change.您看起来需要一个可以与 GUI 进行 2 向“对话”的后台进程,为此,我能想到的最好方法是为您的后台进程创建一个状态机,在后台进程时通知 GUI通过属性更改侦听器更改状态,并允许 GUI 响应此更改。 I will try to show you a code solution, but it may take some time.我将尝试向您展示代码解决方案,但这可能需要一些时间。

最后,我不能得到这个工作,用的SwingWorker,能够得到它具有番石榴工作AsyncEventBus

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

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