4

I currently have the following situation:

I have created a JavaFX application, which contains a screen from which I open a dialog (just by clicking a button on the screen). Then, the user gives input and clicks the apply button. The user input is then sent to a method, which opens some sort of progress dialog (for showing the user the status of the synchronization, which is not important for the problem). I will call this dialog 'MyDialog'. MyDialog is built with the following code:

Dialog<Void> dialog = new Dialog<>();
dialog.initOwner(null);
dialog.initStyle(StageStyle.UNDECORATED);
dialog.setHeaderText("Nieuw product synchroniseren...");
dialog.setResizable(false);

//Load dialog FXML file into the Pane
FXMLLoader fxmlloader = new FXMLLoader();
fxmlloader.setLocation(getClass().getResource("dialogs/MyDialogContent.fxml"));
try {
    dialog.getDialogPane().setContent(fxmlloader.load());
} catch (IOException e) {
    Functions.createExceptionDialog(e);
}
MyDialogContentController childController = fxmlloader.getController();

final ButtonType canceledButtonType = new ButtonType("Cancel", ButtonData.CANCEL_CLOSE);
dialog.getDialogPane().getButtonTypes().add(canceledButtonType);

This works just fine. The ProgressBar, which is shown in MyDialog, indicated the progress of a task. The task is started from a thread. This thread is started from the controller of the upper screen. From within the task, I would like to show an additional dialog at some point, to get some user validation. I will call this dialog 'AlertDialog'. This is the code for that part (which is placed in the Controller of the upper screen, and not in the Controller of MyDialog):

Task<Object> task = new Task<Object>() {

    @Override
    protected Object call() {
        //Show choice dialog
        Alert alert = new Alert(AlertType.CONFIRMATION);
        alert.initOwner(null);
        alert.initStyle(StageStyle.UNDECORATED);

        ButtonType buttonTypeOne = new ButtonType("One");
        ButtonType buttonTypeTwo = new ButtonType("Two");
        ButtonType buttonTypeThree = new ButtonType("Three");
        ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonData.CANCEL_CLOSE);

        alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeTwo, buttonTypeThree, buttonTypeCancel);

        Optional<ButtonType> result = alert.showAndWait();

        if (result.get() == buttonTypeOne){
            //User chose "One";
        } else if (result.get() == buttonTypeTwo) {
            // ... user chose "Two"
        } else if (result.get() == buttonTypeThree) {
            // ... user chose "Three"
        } else {
            // ... user chose CANCEL or closed the dialog
        }
    }
}

Unfortunately, the AlertDialog is not showing, and I get the following error:

Exception in thread "Thread-10" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-10

I already tried the following solutions:

  • Place the AlertDialog code in the MyDialogController, and then call it from the task.
  • Start the thread from MyDialogController, instead of starting in from the upper screen controller. And then call it by 'childController.thread'.

Both of these solutions did not work. I expect it has something to do with loading the FXML file in the DialogPane, and therefore with the thread and from where it is started.

So the questions in this situation are:

  1. Why do I get this error?

  2. Has the error something to do with the AlertDialog not showing?

  3. Should my approach for this part of the code be different? (e.g. without loading an external FXML file in a dialog)

Any help is greatly appreciated!

bashoogzaad
  • 4,611
  • 8
  • 40
  • 65

1 Answers1

6

I feel like I have written this 100 times on SO.. yet again: JavaFX is a single threaded GUI tookit, thus every thing GUI related has to be done on the main JavaFX Thread.

If you try to do something GUI related out of the JavaFX Thread you will get your IllegalStateException: Not on FX application thread.

Optional<ButtonType> result = alert.showAndWait(); is a GUI action because you initialize and show a Dialog. So you should retrieve your user input somewhere else and only do the long-running computations in background.

Good points for user input are for example the various life-cycle hooks of the Task class (like succeeded() or failed()).

eckig
  • 10,964
  • 4
  • 38
  • 52
  • Thanks for your answer! I analyzed the situation a bit further, and it looks like the error already occurs when the first dialog is closed and the second one (which is MyDialog) is opened from the `result.ifPresent()` of the first controller. Could that be the problem? Then I still not understand why the second dialog is just not opened in the current JavaFX application thread.. – bashoogzaad Jan 04 '15 at 11:54
  • Because you're calling `Alert alert = new Alert(...);` from your task, which as you say is executed in a `Thread`. If you need to pause your background thread to wait for user input, use [this technique](http://stackoverflow.com/questions/14941084/javafx2-can-i-pause-a-background-task-service) – James_D Jan 04 '15 at 11:58
  • Okay thanks @James_D, that will solve that problem. Only, the error already occurred when switching between the first and second dialog (both custom dialogs). How can that be explained then? – bashoogzaad Jan 04 '15 at 12:15
  • 1
    In the code you posted, you are explicitly calling `new Alert(...)` on a background thread, which clearly explains why you are getting the error. If you have other code which you think is being executed on the FX Application Thread and is causing the error, update your question with that code (and also show the stack trace and identify the line in the code that is causing the error in the stack trace). – James_D Jan 04 '15 at 12:21
  • Oh never mind, I was updating a Label in the task. I have put it in a `Platform.runlater()` and now the error is gone. I am now going to try to fix the AlertDialog problem. – bashoogzaad Jan 04 '15 at 12:28
  • Ok @James_D, still got one question: I had made a task and thread already before in another dialog, and here it worked just fine (without `Platform.runLater()`. So I looked at the differences and it turned out the problem is `Task protected` vs `Task public`. When `public`, I can simple e.g. set a Label without getting the error. So could that be the entire problem? – bashoogzaad Jan 04 '15 at 12:35
  • No, that has absolutely nothing to do with it. – James_D Jan 04 '15 at 13:13
  • Okay, thanks eckig and James_D for your help. I fixed the problem with a `FutureTask` in a `runLater`, as James_D suggested. – bashoogzaad Jan 04 '15 at 13:58