-1

I am attempting to build a progress bar that is being updated as my application is retrieving and populating data to the GUI. I figured that the progress bar will be reused a lot so I decided to create its own class. However, I don't believe I understand either the Worker/Task or Multi-Threading in general to create a re-usable situation. What would be the recommended approach to creating a progress bar that can listen to the application thread and update the bar accordingly. Here is my attempt:

// Simple Progress Bar View as Pop Up
public class ProgressIndicatorUtil{

    @FXML
    private ProgressBar progressBar;
    @FXML
    private Label statusLabel;
    @FXML
    private Button closeButton;
    @FXML
    private Label valueLabel;

    private Worker worker;
    private Stage stage;

    public void setPopUpStage(Stage stage) {
        this.stage = stage;
    }

    public void setWorker(Worker worker) {
        this.worker = worker;
    }

    public void setLinkToMainPage(Object controller) {

        ((Task<String>) worker).setOnSucceeded(event -> stage.close());

        ((Task<String>) worker).setOnCancelled(event -> {
                closeButton.setVisible(true);
                stage.requestFocus();
                statusLabel.setTextFill(Color.RED);}
        );

        valueLabel.textProperty().bind(Bindings.format("%5.1f%%", worker.progressProperty().multiply(100)));
        progressBar.progressProperty().bind(worker.progressProperty());
        statusLabel.textProperty().bind(worker.messageProperty());
    }

    @FXML
    private void handleClose(ActionEvent e){
        stage.close();
    }
}

The Controller that calls the View Pop-Up and runs the Progress Bar Thread.

public class MyController{

   //Controller calling the view and disabling the main GUI
   private void loadProgressBar(Worker worker){

        try{

            FXMLLoader loader = new FXMLLoader(getClass()
                    .getClassLoader().getResource("main/resources/fxml/ProgressBar.fxml"));
            AnchorPane pane = (AnchorPane)loader.load();
            Stage popUpStage = new Stage();
            popUpStage.initModality(Modality.WINDOW_MODAL);
            Scene scene = new Scene(pane);
            popUpStage.setScene(scene);

            ProgressIndicatorUtil controller = loader.getController();
            controller.setPopUpStage(popUpStage);
            controller.setWorker(worker);
            controller.setLinkToMainPage(this);
            mainPane.setDisable(true);
            popUpStage.showingProperty().addListener((obs, hidden, showing) -> {
                    if(hidden) mainPane.setDisable(false);});
            popUpStage.show();

        }catch(IOException e){
            e.printStackTrace();
        }   
    }



   private void runProgressBar(Worker worker) {
        new Thread((Runnable) worker).start();
   }


    //A user action that runs the progress bar and GUI
    @FXML
    private void aBigProcessingEvent(ActionEvent event) {

        Worker worker = new Task<String>(){

            @Override
            protected String call() throws Exception {        

                updateProgress(0, 3);
                updateMessage("Clearing Data");

                processingEvent01();

                updateProgress(1, 3);
                updateMessage("Retriving Data And Adding To List");

                processingEvent02();

                updateProgress(2, 3);
                updateMessage("Populating Data");

                processingEvent03();

                updateProgress(3, 3);
                return "Finished!";
            }
        };

        loadProgressBar(worker);
        runProgressBar(worker);
    }
}

The program works fine, visually, but it throws an Exception like this (Not On FX Application Thread) and running Platform.runLater() on my "processingEvent" methods will cause my progress bar to be 100% immediately, but it won't throw anymore Exceptions. Any suggestion to how to split the application modification methods and the worker methods apart while keeping the progression connected to the processingEvent methods? Much thanks.

Community
  • 1
  • 1
  • You could try and make an Integer object, then pass it to the threads and increment whenever task is finished. Then set your progress to the value of integer / # of tasks. Make sure you use synchronize when accessing the reference to the object. – Marcin D Sep 15 '15 at 17:10
  • @MarcinDeszczynski You can't do that: it would definitely generate `IllegalStateException`s of the kind the OP describes. Binding to the progress property of the task and calling `updateProgress(...)` is exactly the right way to do this. – James_D Sep 15 '15 at 18:13
  • This code looks right to me; I cannot test it because it is incomplete. Which line throws the `IllegalStateException`? What do the `processingEvent*()` methods do? You should keep any time-consuming methods in the background thread, but wrap any method calls that update the UI in `Platform.runLater(...)`. (The `updateProgress` and `updateMessage` methods are designed to be called from the background thread; they basically handle the calls to `Platform.runLater(...)` for you.) – James_D Sep 15 '15 at 18:14

1 Answers1

2

There is nothing wrong with the (incomplete) code you have posted, so there errors are in other parts of your code. Since the code is incomplete, I have to make some educated guesses as to what is happening. (Note: it is actually much better if you can create complete examples when you post questions, so that you ensure the cause of the issue you are asking about is included.)

Since you are getting an IllegalStateException "Not on the FX Application Thread", you must be updating the UI from a background thread. Since the only code you've posted that runs in a background thread is in the Task you create in aBigProcessingEvent(), the UI updates must be happening in the one or more of the processingEventXX() methods you haven't shown.

If you wrap the calls to processingEventXX() in Platform.runLater(), then you won't see any progressive updates to the progress bar. Platform.runLater() schedules the runnable you provide to be executed on the FX Application Thread and exits immediately. There is no other code in the Task that takes time to run, so the entire task is completed in very little time, and by the time the FX Application Thread renders the next frame, the task is complete and the progress property is at 1.

So presumably your processingEventXX() methods take time to execute, and also update the UI. You must wrap the calls that update the UI in those methods in Platform.runLater(...). The code wrapped in Platform.runLater(...) must not include code that takes a long time to run. I.e. they should look like

private void processingEvent01() {
    // some long-running process here...

    Platform.runLater(() -> {
        // update UI here....
    });

    // some other long-running process here (perhaps)
}
Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks and sorry for the confusion, James. I was not sure if I was supposed to run codes that update the UI within the worker/task thread since it lead to that IllegalStateException. Therefore I thought it was my design flaw and was wondering if maybe I was to run processingEvent01 with long processes outside of Worker worker = new Task(){} function and then somehow allow the background thread/progress bar to listen for update. I guess I will have to play around with Platform.runLater then. – AnotherNewbplusplus Sep 15 '15 at 19:23
  • 1
    Read my answer [here](http://stackoverflow.com/questions/30249493/using-threads-to-make-database-requests) and see if it helps. – James_D Sep 15 '15 at 19:39
  • Yes, that link definitely helped me understand the basics of JavaFX multi-threading and the above answer to target GUI update using Platform.runLater() was definitely what I needed. Thanks again! – AnotherNewbplusplus Sep 15 '15 at 22:09