我有一个 Java 8 Swing 应用程序,当用户单击一个新按钮时,需要向它添加一个耗时的操作。我认为这是一个完美的用例,SwingWorker
尽管我以前从未写过。完整的源代码和可重现的 Swing 应用程序在这里。
当用户点击一个按钮时,应用程序必须从几个不同的来源收集信息,然后开始这个后台操作。它将计算一个InputAnalysis
,然后将其InputAnalysis
返回到 EDT 中的单击处理程序以更新 UI。虽然它有效,但我希望它也能更新JProgressBar
,以便用户看到正在取得的进展。迄今为止我最好的尝试:
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);
}
}
}
当我注释掉try-catch
包含我的analysisService_analyze()
调用的块,并取消注释 my 的代码时MegaWorker
,进度条仍然没有正确更新。
不是必需的,因为上面提供了SSCCE的所有必要代码,但是如果您有兴趣快速构建和运行此代码,我已经在 GitHub 上准备了这个SimpleApp存储库以节省您一些时间。但不是回答这个问题所必需的,上面提供了所有代码。100%。