2

I have a problem like the one exposed at JavaFX indeterminate progress bar while doing a process , but I use a javafx dialog Alert in order to show a dialog with inside a progress bar using a code like this:

Alert busyWaitAlert = new Alert(
Alert.AlertType.INFORMATION);
busyWaitAlert.setContentText("Operation in progress");                
ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.FINISH);                    
busyWaitAlert.getButtonTypes().setAll(cancelButton);
busyWaitAlert.setTitle("Running Operation");
busyWaitAlert.setHeaderText("Please wait... ");
ProgressBar progressBar = new ProgressBar();                   
busyWaitAlert.setGraphic(progressBar);
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);

Task<Boolean> task;
task = new Task<Boolean>() {
   @Override 
    public Boolean call() throws Exception {       
      Collections.sort(LnkListImportedToDb, new importDataToDb.DataItemImportedToDbTimeCmp1()); 
      return true;
    }
 };

   task.setOnSucceeded((e) -> {                        
        busyWaitAlert.close();                      
    });

    progressBar.progressProperty().bind(task.progressProperty());
    busyWaitAlert.contentTextProperty().bind(task.messageProperty());

    new Thread(task).start();      

The problem is that inside a Task I call a Collection.Sort of which I do not know the duration and I can't do an updateMessage within the "Task call" method, without this the Alert dialog freeze. I think to solve the problem by inserting another Task that manages the Collection.sort within the call of the first Task, is there a better solution ?

famedoro
  • 1,223
  • 2
  • 17
  • 41
  • Please try to create a [mcve]. You might even discover the problem when reducing the issue to the bare minimum. At the very least you should post your `Task` implementation as well as how you are executing said `Task`. In addition, I'm not sure I understand why you can't call `updateMessage` from inside `Task#call()`..? Nor how not calling `updateMessage` freezes the `Alert` UI.. Though I highly doubt that calling _another_ `Task` from inside the first `Task` is the solution. – Slaw Jul 17 '18 at 21:04
  • @Slaw if I call updateMessage in Task#call() he doesn't freeze because, I think, this causes a continuous updating of the Alert dialog – famedoro Jul 18 '18 at 07:07
  • @Slaw I have posted my Task implentation – famedoro Jul 18 '18 at 16:35
  • Your code looks like it should work. There are superficial issues like formatting (which could be the result of copying the code to SO) and not following proper Java naming conventions, but it looks functionally correct. Take a look at my answer for it goes over how you can use `updateMessage` despite not knowing the duration of the `Task`. I also give a full example that sorts a randomly generated `List` on a background thread while displaying an `Alert` to the user. – Slaw Jul 18 '18 at 16:44

1 Answers1

3

The purpose of Task, or more generally Worker, is to run a long running process on a background thread while being able to update the user as to its progress. The Task class gives you many methods that allow safe communication with the JavaFX Application Thread such as updateProgress, updateMessage, and setOnSucceeded. You do not need to use these features to use a Task, however; and not using these features does not functionally harm the Task in any way.

For instance, you could have an extremely simple Task that looks like this:

public class TaskImpl<E> extends Task<Void> {
    private final List<E> list;

    public TaskImpl(List<E> list) { this.list = list; }

    @Override protected Void call() throws Exception {
        list.sort(null);
        return null;
    }
}

Here, the code is not taking advantage of the Task and is simply being used to sort a List on a background thread. Outside code can still observe the Task with no ill effect. Your Alert, for example, will not freeze because the message and progress properties aren't being updated.

If you are not going to use the functionality of a Task, however, you may as well just use a plain old Runnable or Callable. The above is functionally equivalent to:

() -> list.sort(null);

There is something both Runnable and Task (which implements Runnable) have in common: neither one takes care of running itself on a separate thread for you. You have to actually pass the work to another thread. You can do this by either creating a Thread directly or, the better option, using the Executor framework provided in the java.util.concurrent package.

With all that said, I'm not sure I understand what the issues you're experiencing are. Since you don't know how long a call to List.sort will take the Task's progress property should simply stay indeterminate. If you want to display a message you could do something like:

@Override protected Void call() {
    updateMessage("Sorting...");
    list.sort(null);
    updateMessage("Sorting complete.");
    updateProgress(1, 1); // jumps progress from indeterminate to 100%
    return null;
}

Then you'd bind the appropriate controls to the appropriate properties, add the controls to an Alert, and display the Alert. Here's a full working example on how to do this:

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main extends Application {

    private List<Integer> integerList;

    @Override
    public void init() {
        /*
         * If there is at least one parameter attempt to parse the
         * first one as an Integer. This value will be used as the
         * size of the randomly generated "integerList".
         *
         * If there are no parameters then default to 10 million.
         */
        List<String> params = getParameters().getRaw();
        if (!params.isEmpty()) {
            generateRandomList(Integer.parseInt(params.get(0)));
        } else {
            generateRandomList(10_000_000);
        }
    }

    private void generateRandomList(int size) {
        System.out.printf("Generating random list of %,d integers...%n", size);
        Random r = new Random();
        integerList = Stream.generate(() -> r.nextInt(size))
                .limit(size)
                .collect(Collectors.toList());
        System.out.println("List generated.");
    }

    @Override
    public void start(Stage primaryStage) {
        System.out.println("Building scene graph...");
        ListView<Integer> listView = new ListView<>();
        listView.setPrefWidth(500);
        listView.getItems().addAll(integerList);

        Button btn = new Button("Sort List");
        btn.setOnAction(ae -> {
            ae.consume();
            btn.setDisable(true);

            /*
             * Here we create the task and configure it to set items of the listView
             * to the result. We do this because we are sorting "integerList" which
             * we had *copied* into the ListView. If  we sorted the items of the ListView
             * then we would be altering the UI from a background thread (big no-no!).
             * Therefore we need to re-copy the "integerList" into the ListView upon
             * completion of the task.
             */
            SortingTask<Integer> task = new SortingTask<>(integerList, null);
            task.setOnSucceeded(wse -> listView.getItems().setAll(task.getValue()));

            Alert alert = createProgressAlert(primaryStage, task);
            executeTask(task);
            alert.show();
        });

        VBox root = new VBox(btn, listView);
        root.setSpacing(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(20));
        VBox.setVgrow(listView, Priority.ALWAYS);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Progress Alert Example");
        primaryStage.setResizable(false);
        primaryStage.show();
        System.out.println("Primary stage displayed.");
    }

    // executes task on a separate, daemon thread
    private void executeTask(Task<?> task) {
        Thread t = new Thread(task, "sorting-thread");
        t.setDaemon(true);
        t.start();
    }

    // creates the Alert and necessary controls to observe the task
    private Alert createProgressAlert(Stage owner, Task<?> task) {
        Alert alert = new Alert(Alert.AlertType.NONE);
        alert.initOwner(owner);
        alert.titleProperty().bind(task.titleProperty());
        alert.contentTextProperty().bind(task.messageProperty());

        ProgressIndicator pIndicator = new ProgressIndicator();
        pIndicator.progressProperty().bind(task.progressProperty());
        alert.setGraphic(pIndicator);

        alert.getDialogPane().getButtonTypes().add(ButtonType.OK);
        alert.getDialogPane().lookupButton(ButtonType.OK)
                .disableProperty().bind(task.runningProperty());

        alert.getDialogPane().cursorProperty().bind(
                Bindings.when(task.runningProperty())
                    .then(Cursor.WAIT)
                    .otherwise(Cursor.DEFAULT)
        );

        return alert;
    }

    // The Task implementation

    private static class SortingTask<E> extends Task<List<E>> {

        private final List<E> list;
        private final Comparator<? super E>  comparator;

        private SortingTask(List<E> list, Comparator<? super E> comparator) {
            this.list = Objects.requireNonNull(list);
            this.comparator = comparator; // if null then natural order is used by List.sort
            updateTitle("Sorting Task");
        }

        @Override
        protected List<E> call() throws Exception {
            updateMessage("Sorting list...");
            list.sort(comparator);
            updateMessage("Sorting complete.");
            updateProgress(1, 1);
            return list;
        }

        @Override
        protected void running() {
            System.out.println("Sorting task is running...");
        }

        @Override
        protected void succeeded() {
            System.out.println("Sorting task successful.");
        }

    }

}

If you wish to customize how large the randomly generated list will be you can pass an integer as an argument to this program. If more than one argument is passed all but the first is ignored. If no arguments are given then the size defaults to 10,000,000 (10 million). Depending on your computer you may want to increase or decrease the size of the list so the Task doesn't complete too quickly (or take too long).

Slaw
  • 37,820
  • 8
  • 53
  • 80