Home > OS >  Iterate thread strategy for the FXML ProgressBar
Iterate thread strategy for the FXML ProgressBar

Time:04-22

I have coded myself into a corner here. In my FXML file, I declare a progress bar and an upload progress label (for a batch upload):

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" />

Then I have a UI controller where I import all my elements from FXML:

UI_Controller.java

@FXML Label uploadProgressLabel;
@FXML ProgressBar uploadProgressBar;

Later in the UI Controller, there is a button whose action is to update the "upload" progress bar, and it doesn't. I've tried a few different thread / task strategies and none of them seem to work while they are running.

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

...

My question is, how can I get the progress bar / label to update while they are on the main thread? When I try moving them off the main thread I get an error about them not being on the JavaFX thread. I've also tried moving the logic over into a task, which looked like this (and then had a run of the task on the main thread) also to no avail:

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

Some guidance would be appreciated.

CodePudding user response:

Like most other UI toolkits, JavaFX is single threaded, with the FX Application Thread being responsible for processing user events and rendering the UI. This means:

  1. You must not update UI controls from a background thread. JavaFX will throw IllegalStateExceptions in many (though not all) cases if you do this. (Note that Swing doesn't throw exceptions; it just leaves you vulnerable to arbitrary failure at some indeterminate point.)
  2. Long-running processes (such as your file upload) must not be run on the FX Application Thread. Since this thread is responsible for rendering the UI and processing user events, no UI updates (such as updating the label and progress bar) will be possible until the long-running process is complete. Additionally, the UI will be unresponsive during this time.

You should use a Task to implement the long running process, and run the task on a background thread. The Task has thread-safe update methods which will update its properties (such as progress and message) on the FX Application Thread, so you can safely bind properties of UI elements to these properties. It also has onSucceeded and onFailed handlers, which are also executed on the FX Application thread. The onSucceeded handler can access any return value from the task.

So your code should look something like:

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