我有一个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;
}
我的Swing / GUI应用程序,包括EDT代码:
SimpleApp
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-catch
的AnalysisService
块,并取消注释AnalysisWorker
的代码时,进度栏仍未正确更新。不是必需的,因为上面提供了SSCCE的所有必需代码,但是如果您快速构建和运行此代码,我已经在GitHub上准备了此SimpleApp repo,以节省您一些时间。但是,不必回答这个问题,上面提供了所有代码。 100%。有兴趣
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();
}
}