FXML ProgressBar 的迭代线程策略
Iterate thread strategy for the FXML ProgressBar
我在这里把自己写进了一个角落。在我的 FXML 文件中,我声明了一个进度条和一个上传进度标签(用于批量上传):
Program_view.fxml
<Label fx:id="uploadProgressLabel" layoutX="384" layoutY="579" text="Upload Progress">
<font>
<Font size="18" />
</font>
</Label>
...
<ProgressBar fx:id="uploadProgressBar" layoutX="185" layoutY="606" prefHeight="18" prefWidth="534" progress="0" />
然后我有一个 UI 控制器,我从 FXML 导入所有元素:
UI_Controller.java
@FXML Label uploadProgressLabel;
@FXML ProgressBar uploadProgressBar;
稍后在UI控制器中,有一个按钮,其作用是更新“上传”进度条,但它没有。我尝试了几种不同的线程/任务策略,其中 none 似乎在 运行ning 时工作。
UI_Controller.java
@FXML
protected void distributeSetButtonClick() {
//In a previous version of this project using swing, I tossed this whole function into a new thread and that made the progress bar happy
//new Thread(() -> {
final boolean[] done = {false};
if (logTextArea.getText().equalsIgnoreCase("This is your console log, logs relating to uploading your set will appear here")) {
logTextArea.setText("");
}
//Upload each file and iterate label + counter
for (int i = 1; i <= uploadImages.size(); i++) {
System.out.println("Test: " + i);
uploadProgressLabel.setText("Uploading image " + i + "/" + uploadImages.size());
File f = uploadImages.get(i - 1);
mediaIds.add(UploadFile.uploadFile(f, logTextArea, i - 1, uploadImages.size()));
double currentProgress = (1.0 / uploadImages.size()) * i;
uploadProgressBar.setProgress(currentProgress);
}
uploadProgressLabel.setText("Completed uploading: " + uploadImages.size() + " images");
String areaUpdate = filesSelectedTextArea.getText();
if (mediaIds.size() == uploadImages.size()) {
areaUpdate += "\r\n\r\n All Images uploaded successfully";
} else {
areaUpdate += "\r\n\r\n One or more files had an error while uploading";
}
filesSelectedTextArea.setText(areaUpdate);
}
...
我的问题是,如何让进度条/标签在主线程上更新?当我尝试将它们从主线程中移出时,我收到一条关于它们不在 JavaFX 线程上的错误。我也试过将逻辑移到任务中,看起来像这样(然后在主线程上有一个 运行 任务)也无济于事:
Tasker.java
public static Task<Void> updateProgressBar(ProgressBar p, double value) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
p.setProgress(value);
return null;
}
};
p.progressProperty().bind(task.progressProperty());
return task;
}
一些指导将不胜感激。
与大多数其他 UI 工具包一样,JavaFX 是单线程的,FX 应用程序线程负责处理用户事件和呈现 UI。这意味着:
- 您不得从后台线程更新UI控件。如果您这样做,JavaFX 将在许多(尽管不是全部)情况下抛出
IllegalStateException
s。 (请注意,Swing 不会抛出异常;它只会让您容易在某个不确定的点出现任意故障。)
- Long-running 进程(例如您的文件上传)不得 在 FX 应用程序线程上 运行。由于此线程负责呈现 UI 和处理用户事件,因此在 long-running 过程完成之前,无法进行 UI 更新(例如更新标签和进度条)。此外,UI 在此期间将没有响应。
您应该使用 Task
来实现较长的 运行ning 进程,并 运行 在后台线程上执行该任务。 Task
具有 thread-safe update
方法,这些方法将在 FX 应用程序线程上更新其属性(例如 progress
和 message
),因此您可以安全地绑定属性UI 个元素添加到这些属性中。它还具有 onSucceeded
和 onFailed
处理程序,它们也在 FX 应用程序线程上执行。 onSucceeded
处理程序可以访问任务中的任何 return 值。
所以您的代码应该类似于:
@FXML
protected void distributeSetButtonClick(){
//In a previous version of this project using swing, I tossed this whole function into a new thread and that made the progress bar happy
Task<String> task = new Task<>() {
@Override
protected String call() throws Exception {
final boolean[] done = {false};
//Upload each file and iterate label + counter
for (int i = 1; i <= uploadImages.size(); i++) {
System.out.println("Test: " + i);
updateMessage("Uploading image " + i + "/" + uploadImages.size());
File f = uploadImages.get(i - 1);
mediaIds.add(UploadFile.uploadFile(f, logTextArea, i - 1, uploadImages.size()));
updateProgress(i, uploadImages.size());
}
if (mediaIds.size() == uploadImages.size()) {
return "All Images uploaded successfully";
} else {
return "One or more files had an error while uploading";
}
}
};
if (logTextArea.getText().equalsIgnoreCase("This is your console log, logs relating to uploading your set will appear here")) {
logTextArea.setText("");
}
task.setOnSucceeded(event -> {
filesSelectedTextArea.append("\n\n"+task.getValue());
uploadProgressLabel.setText("Completed uploading: " + uploadImages.size() + " images");
});
uploadProgressLabel.textProperty().unbind()
uploadProgressLabel.textProperty().bind(task.messageProperty());
uploadProgressBar.progressProperty().unbind();
uploadProgressBar.progressProperty().bind(task.progressProperty());
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
我在这里把自己写进了一个角落。在我的 FXML 文件中,我声明了一个进度条和一个上传进度标签(用于批量上传):
Program_view.fxml
<Label fx:id="uploadProgressLabel" layoutX="384" layoutY="579" text="Upload Progress">
<font>
<Font size="18" />
</font>
</Label>
...
<ProgressBar fx:id="uploadProgressBar" layoutX="185" layoutY="606" prefHeight="18" prefWidth="534" progress="0" />
然后我有一个 UI 控制器,我从 FXML 导入所有元素:
UI_Controller.java
@FXML Label uploadProgressLabel;
@FXML ProgressBar uploadProgressBar;
稍后在UI控制器中,有一个按钮,其作用是更新“上传”进度条,但它没有。我尝试了几种不同的线程/任务策略,其中 none 似乎在 运行ning 时工作。
UI_Controller.java
@FXML
protected void distributeSetButtonClick() {
//In a previous version of this project using swing, I tossed this whole function into a new thread and that made the progress bar happy
//new Thread(() -> {
final boolean[] done = {false};
if (logTextArea.getText().equalsIgnoreCase("This is your console log, logs relating to uploading your set will appear here")) {
logTextArea.setText("");
}
//Upload each file and iterate label + counter
for (int i = 1; i <= uploadImages.size(); i++) {
System.out.println("Test: " + i);
uploadProgressLabel.setText("Uploading image " + i + "/" + uploadImages.size());
File f = uploadImages.get(i - 1);
mediaIds.add(UploadFile.uploadFile(f, logTextArea, i - 1, uploadImages.size()));
double currentProgress = (1.0 / uploadImages.size()) * i;
uploadProgressBar.setProgress(currentProgress);
}
uploadProgressLabel.setText("Completed uploading: " + uploadImages.size() + " images");
String areaUpdate = filesSelectedTextArea.getText();
if (mediaIds.size() == uploadImages.size()) {
areaUpdate += "\r\n\r\n All Images uploaded successfully";
} else {
areaUpdate += "\r\n\r\n One or more files had an error while uploading";
}
filesSelectedTextArea.setText(areaUpdate);
}
...
我的问题是,如何让进度条/标签在主线程上更新?当我尝试将它们从主线程中移出时,我收到一条关于它们不在 JavaFX 线程上的错误。我也试过将逻辑移到任务中,看起来像这样(然后在主线程上有一个 运行 任务)也无济于事:
Tasker.java
public static Task<Void> updateProgressBar(ProgressBar p, double value) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
p.setProgress(value);
return null;
}
};
p.progressProperty().bind(task.progressProperty());
return task;
}
一些指导将不胜感激。
与大多数其他 UI 工具包一样,JavaFX 是单线程的,FX 应用程序线程负责处理用户事件和呈现 UI。这意味着:
- 您不得从后台线程更新UI控件。如果您这样做,JavaFX 将在许多(尽管不是全部)情况下抛出
IllegalStateException
s。 (请注意,Swing 不会抛出异常;它只会让您容易在某个不确定的点出现任意故障。) - Long-running 进程(例如您的文件上传)不得 在 FX 应用程序线程上 运行。由于此线程负责呈现 UI 和处理用户事件,因此在 long-running 过程完成之前,无法进行 UI 更新(例如更新标签和进度条)。此外,UI 在此期间将没有响应。
您应该使用 Task
来实现较长的 运行ning 进程,并 运行 在后台线程上执行该任务。 Task
具有 thread-safe update
方法,这些方法将在 FX 应用程序线程上更新其属性(例如 progress
和 message
),因此您可以安全地绑定属性UI 个元素添加到这些属性中。它还具有 onSucceeded
和 onFailed
处理程序,它们也在 FX 应用程序线程上执行。 onSucceeded
处理程序可以访问任务中的任何 return 值。
所以您的代码应该类似于:
@FXML
protected void distributeSetButtonClick(){
//In a previous version of this project using swing, I tossed this whole function into a new thread and that made the progress bar happy
Task<String> task = new Task<>() {
@Override
protected String call() throws Exception {
final boolean[] done = {false};
//Upload each file and iterate label + counter
for (int i = 1; i <= uploadImages.size(); i++) {
System.out.println("Test: " + i);
updateMessage("Uploading image " + i + "/" + uploadImages.size());
File f = uploadImages.get(i - 1);
mediaIds.add(UploadFile.uploadFile(f, logTextArea, i - 1, uploadImages.size()));
updateProgress(i, uploadImages.size());
}
if (mediaIds.size() == uploadImages.size()) {
return "All Images uploaded successfully";
} else {
return "One or more files had an error while uploading";
}
}
};
if (logTextArea.getText().equalsIgnoreCase("This is your console log, logs relating to uploading your set will appear here")) {
logTextArea.setText("");
}
task.setOnSucceeded(event -> {
filesSelectedTextArea.append("\n\n"+task.getValue());
uploadProgressLabel.setText("Completed uploading: " + uploadImages.size() + " images");
});
uploadProgressLabel.textProperty().unbind()
uploadProgressLabel.textProperty().bind(task.messageProperty());
uploadProgressBar.progressProperty().unbind();
uploadProgressBar.progressProperty().bind(task.progressProperty());
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}