我使用 SwingWorker 吗?
Do I use a SwingWorker?
当我的代码发送 http 调用时,我希望它更改 window 上的 JLabel
(基本上显示在 http 调用结束之前大约剩余多少时间)。由于我在这里问的另一个问题,我一直在研究 SwingWorkers
,但我不确定如何使用它。我正在编写的代码基本上有一个循环来发送调用,每次计时 运行 调用需要多长时间,计算剩余的大概时间,然后将其发送到 JLabel
(注意 JLabel在不同的实例化对象中)。
大多数 SwingWorker
示例显示了一个在后台继续运行的函数,该函数不受工作线程的影响(例如,一个完全基于时间而不是被代码更改的计数器)。如果是这种情况,那么 JLabel
的更改不只是工作线程的一部分,因为它是通过新循环的代码 运行s -> 计算时间并进行调用 -> 更改 JLabel
?我可能错了,但是我如何让代码而不是独立线程更改 JLabel?
我的一个问题是,当我最初设置代码时,JLabel
.
中没有任何变化
这是我的代码:
package transcription.windows;
import javax.swing.*;
import java.awt.*;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class PleaseWaitWindow {
private JLabel pleaseWaitJLabel = new JLabel("Please wait");
private GridBagConstraints containerGbc = new GridBagConstraints();
private Container contentPaneContainer = new Container();
private JFrame pleaseWaitJFrame;
public JLabel getPleaseWaitJLabel() {
return pleaseWaitJLabel;
}
public JFrame setPleaseWaitWindow() {
pleaseWaitJFrame = new JFrame();
contentPaneContainer = setContentPane();
pleaseWaitJFrame.setContentPane(contentPaneContainer);
pleaseWaitJFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
pleaseWaitJFrame.setTitle("");
pleaseWaitJFrame.setSize(350, 150);
pleaseWaitJFrame.setLocationRelativeTo(null);
pleaseWaitJFrame.setVisible(true);
return pleaseWaitJFrame;
}
private Container setContentPane() {
containerGbc.insets.bottom = 1;
containerGbc.insets.top = 2;
containerGbc.insets.right = 1;
containerGbc.insets.left = 1;
containerGbc.weightx = 1;
containerGbc.weighty = 1;
contentPaneContainer.setLayout(new GridBagLayout());
contentPaneContainer.setSize(800, 700);
setPleaseWaitJLabel();
return contentPaneContainer;
}
private void setPleaseWaitJLabel() {
containerGbc.gridx = 2;
containerGbc.gridy = 5;
containerGbc.gridwidth = 2;
containerGbc.gridheight = 1;
contentPaneContainer.add(pleaseWaitJLabel, containerGbc);
}
public void setJLabelDisplay(String displayTime) {
pleaseWaitJLabel.setText(displayTime);
}
public void closeWindow() {
pleaseWaitJFrame.dispose();
}
}
作为 ServiceUpload 一部分的方法 class:
public String cuttingLoop(String mpBase64Piece, String jobName, String email) {
Integer numberOfPiecesMinusEnd = (int) Math.ceil(mpBase64Piece.length() / 500000.0);
List<String> base64List = new ArrayList<>();
for (int i = 0; i < numberOfPiecesMinusEnd; i++) {
if (mpBase64Piece.length() >= 500000) {
base64List.add(mpBase64Piece.substring(0, 500000));
mpBase64Piece = mpBase64Piece.substring(500000);
}
}
base64List.add(mpBase64Piece);
pleaseWaitWindow = new PleaseWaitWindow();
pleaseWaitWindow.setPleaseWaitWindow();
for (int n = 0; n < base64List.size(); n++) {
numberOfLoopsLeft = numberOfPiecesMinusEnd - n;
Stopwatch stopwatch = null;
String tag;
Stopwatch.createStarted();
if (base64List.get(n) != null) {
if (n == 0) {
tag = "start";
} else if (n == base64List.size() - 1) {
tag = "end";
} else {
tag = "middle";
}
stopwatch = Stopwatch.createStarted();
response = providerUpload.executeUploadHttp(base64List.get(n), jobName, tag, email);
stopwatch.stop();
}
long oneLoopTime = stopwatch.elapsed(TimeUnit.SECONDS);
pleaseWaitWindow.setJLabelDisplay(numberOfLoopsLeft*oneLoopTime+" seconds remaining");
LOGGER.info("complete");
}
pleaseWaitWindow.closeWindow();
return response;
}
我的一个问题是,当 'SwingWorker' 未与上述代码一起使用时,代码未显示 'JLabel'。
最好将您的代码拆分为职责范围。让我们来看看三个: 1. worker(即上传); 2.显示(即JLabel更新); 3. 两者的整合(前两者是相互独立的,所以你需要一些东西把它们绑在一起)。
从实际工作中抽象出来,可以使用标准接口。第一个只是 Runnable
,即不带任何参数,也不 returning 任何东西。第二个是 Consumer<String>
,因为它需要 String
(显示)但不需要 return 任何东西。第三个将是你的主要控制。
让我们从控件开始,因为这很简单:
Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);
这将在一个单独的线程中启动 worker,这听起来就像您想要的那样。
这是您的上传者:
Consumer<String> display = // tbd, see below
Runnable worker = () -> {
String[] progress = {"start", "middle", "finish"};
for (String pr : progress) {
display.accept(pr);
Thread.sleep(1000); // insert your code here
}
}
请注意,这个工作人员实际上确实依赖于消费者;这有点“不干净”,但可以。
现在进行展示。将其定义为 Consumer<String>
,它足够抽象,我们可以在控制台上打印进度。
Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);
但是您想要更新 JLabel
;所以消费者看起来像
Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);
您的实际问题
因此,如果您这样做,您会注意到您的标签文本没有如您预期的那样得到更新。那是因为 label.setText(s)
在 worker 所在的线程中执行 运行ning;它需要插入到 Swing 线程中。这就是 SwingWorker
的用武之地。
SwingWorker
有一个 progress
字段,您可以将其用于标签;它还有一个 doInBackground()
这是您实际的上传工作线程。所以你最终得到
class UploadSwingWorker {
public void doInBackground() {
for(int i = 0; i < 3; i++) {
setProgress(i);
Thread.sleep(1000); // again, your upload code
}
}
}
那如何更新您的标签? setProgress
引发了一个 PropertyChangeEvent
你可以拦截;这是使用带有签名
的 PropertyChangeListener
完成的
void propertyChange(PropertyChangeEvent e)
这是一个函数式接口,因此您可以使用 lambda 来实现它,在您的情况下
String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
String value = displays[index];
label.setText(value);
}
您可以使用
将其添加到SwingWorker
SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker
更简单
请注意,我自己从未以这种方式使用过 SwingWorker
。解决 GUI 未从您的工作线程中更新的问题的更简单方法是使用 SwingUtilities.invokeLater()
.
调用 GUI 更新
回到最初Consumer<String>
我提出来了,你可以的
Consumer<String> display = s -> SwingUtilities.invokeLater(
() -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
);
应该就可以了。这允许您让您的工作人员保持更抽象的 Runnable
并使用通常的调度机制来 运行 它(例如 ExecutorService.submit()
或 CompletableFuture.runAsync()
),同时仍然允许更新GUI 也同样简单。
当我的代码发送 http 调用时,我希望它更改 window 上的 JLabel
(基本上显示在 http 调用结束之前大约剩余多少时间)。由于我在这里问的另一个问题,我一直在研究 SwingWorkers
,但我不确定如何使用它。我正在编写的代码基本上有一个循环来发送调用,每次计时 运行 调用需要多长时间,计算剩余的大概时间,然后将其发送到 JLabel
(注意 JLabel在不同的实例化对象中)。
大多数 SwingWorker
示例显示了一个在后台继续运行的函数,该函数不受工作线程的影响(例如,一个完全基于时间而不是被代码更改的计数器)。如果是这种情况,那么 JLabel
的更改不只是工作线程的一部分,因为它是通过新循环的代码 运行s -> 计算时间并进行调用 -> 更改 JLabel
?我可能错了,但是我如何让代码而不是独立线程更改 JLabel?
我的一个问题是,当我最初设置代码时,JLabel
.
这是我的代码:
package transcription.windows;
import javax.swing.*;
import java.awt.*;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class PleaseWaitWindow {
private JLabel pleaseWaitJLabel = new JLabel("Please wait");
private GridBagConstraints containerGbc = new GridBagConstraints();
private Container contentPaneContainer = new Container();
private JFrame pleaseWaitJFrame;
public JLabel getPleaseWaitJLabel() {
return pleaseWaitJLabel;
}
public JFrame setPleaseWaitWindow() {
pleaseWaitJFrame = new JFrame();
contentPaneContainer = setContentPane();
pleaseWaitJFrame.setContentPane(contentPaneContainer);
pleaseWaitJFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
pleaseWaitJFrame.setTitle("");
pleaseWaitJFrame.setSize(350, 150);
pleaseWaitJFrame.setLocationRelativeTo(null);
pleaseWaitJFrame.setVisible(true);
return pleaseWaitJFrame;
}
private Container setContentPane() {
containerGbc.insets.bottom = 1;
containerGbc.insets.top = 2;
containerGbc.insets.right = 1;
containerGbc.insets.left = 1;
containerGbc.weightx = 1;
containerGbc.weighty = 1;
contentPaneContainer.setLayout(new GridBagLayout());
contentPaneContainer.setSize(800, 700);
setPleaseWaitJLabel();
return contentPaneContainer;
}
private void setPleaseWaitJLabel() {
containerGbc.gridx = 2;
containerGbc.gridy = 5;
containerGbc.gridwidth = 2;
containerGbc.gridheight = 1;
contentPaneContainer.add(pleaseWaitJLabel, containerGbc);
}
public void setJLabelDisplay(String displayTime) {
pleaseWaitJLabel.setText(displayTime);
}
public void closeWindow() {
pleaseWaitJFrame.dispose();
}
}
作为 ServiceUpload 一部分的方法 class:
public String cuttingLoop(String mpBase64Piece, String jobName, String email) {
Integer numberOfPiecesMinusEnd = (int) Math.ceil(mpBase64Piece.length() / 500000.0);
List<String> base64List = new ArrayList<>();
for (int i = 0; i < numberOfPiecesMinusEnd; i++) {
if (mpBase64Piece.length() >= 500000) {
base64List.add(mpBase64Piece.substring(0, 500000));
mpBase64Piece = mpBase64Piece.substring(500000);
}
}
base64List.add(mpBase64Piece);
pleaseWaitWindow = new PleaseWaitWindow();
pleaseWaitWindow.setPleaseWaitWindow();
for (int n = 0; n < base64List.size(); n++) {
numberOfLoopsLeft = numberOfPiecesMinusEnd - n;
Stopwatch stopwatch = null;
String tag;
Stopwatch.createStarted();
if (base64List.get(n) != null) {
if (n == 0) {
tag = "start";
} else if (n == base64List.size() - 1) {
tag = "end";
} else {
tag = "middle";
}
stopwatch = Stopwatch.createStarted();
response = providerUpload.executeUploadHttp(base64List.get(n), jobName, tag, email);
stopwatch.stop();
}
long oneLoopTime = stopwatch.elapsed(TimeUnit.SECONDS);
pleaseWaitWindow.setJLabelDisplay(numberOfLoopsLeft*oneLoopTime+" seconds remaining");
LOGGER.info("complete");
}
pleaseWaitWindow.closeWindow();
return response;
}
我的一个问题是,当 'SwingWorker' 未与上述代码一起使用时,代码未显示 'JLabel'。
最好将您的代码拆分为职责范围。让我们来看看三个: 1. worker(即上传); 2.显示(即JLabel更新); 3. 两者的整合(前两者是相互独立的,所以你需要一些东西把它们绑在一起)。
从实际工作中抽象出来,可以使用标准接口。第一个只是 Runnable
,即不带任何参数,也不 returning 任何东西。第二个是 Consumer<String>
,因为它需要 String
(显示)但不需要 return 任何东西。第三个将是你的主要控制。
让我们从控件开始,因为这很简单:
Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);
这将在一个单独的线程中启动 worker,这听起来就像您想要的那样。
这是您的上传者:
Consumer<String> display = // tbd, see below
Runnable worker = () -> {
String[] progress = {"start", "middle", "finish"};
for (String pr : progress) {
display.accept(pr);
Thread.sleep(1000); // insert your code here
}
}
请注意,这个工作人员实际上确实依赖于消费者;这有点“不干净”,但可以。
现在进行展示。将其定义为 Consumer<String>
,它足够抽象,我们可以在控制台上打印进度。
Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);
但是您想要更新 JLabel
;所以消费者看起来像
Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);
您的实际问题
因此,如果您这样做,您会注意到您的标签文本没有如您预期的那样得到更新。那是因为 label.setText(s)
在 worker 所在的线程中执行 运行ning;它需要插入到 Swing 线程中。这就是 SwingWorker
的用武之地。
SwingWorker
有一个 progress
字段,您可以将其用于标签;它还有一个 doInBackground()
这是您实际的上传工作线程。所以你最终得到
class UploadSwingWorker {
public void doInBackground() {
for(int i = 0; i < 3; i++) {
setProgress(i);
Thread.sleep(1000); // again, your upload code
}
}
}
那如何更新您的标签? setProgress
引发了一个 PropertyChangeEvent
你可以拦截;这是使用带有签名
PropertyChangeListener
完成的
void propertyChange(PropertyChangeEvent e)
这是一个函数式接口,因此您可以使用 lambda 来实现它,在您的情况下
String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
String value = displays[index];
label.setText(value);
}
您可以使用
将其添加到SwingWorker
SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker
更简单
请注意,我自己从未以这种方式使用过 SwingWorker
。解决 GUI 未从您的工作线程中更新的问题的更简单方法是使用 SwingUtilities.invokeLater()
.
回到最初Consumer<String>
我提出来了,你可以的
Consumer<String> display = s -> SwingUtilities.invokeLater(
() -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
);
应该就可以了。这允许您让您的工作人员保持更抽象的 Runnable
并使用通常的调度机制来 运行 它(例如 ExecutorService.submit()
或 CompletableFuture.runAsync()
),同时仍然允许更新GUI 也同样简单。