Return result from javafx platform runlater

I am working on JavaFX application, in my scenario is to show a password prompt created in JavaFX which takes password with two option OK and Cancel. I have returned the password entered by user.

My class of showing password dialog is -

public static String showPasswordDialog(String title, String message, Stage parentStage, double w, double h) {
    try {
        Stage stage = new Stage();
        PasswordDialogController controller = (PasswordDialogController) Utility.replaceScene("Password.fxml", stage);
        passwordDialogController.init(stage, message, "/images/password.png");
        if (parentStage != null) {
            stage.initOwner(parentStage);
        }
        stage.initModality(Modality.WINDOW_MODAL);
        stage.initStyle(StageStyle.UTILITY);
        stage.setResizable(false);
        stage.setWidth(w);
        stage.setHeight(h);                
        stage.showAndWait();
        return controller.getPassword(); 
    } catch (Exception ex) {
         return null;
    }

My code where to show password prompt is below, actually this prompt will be shown over other UI, so I need to inclose this inside Platform.runlater(), otherwise it throws Not on FX application thread. I need this password prompt to be shown until I get correct one. How can I get value of password if I inclosed showing password inside runlater.

Is there any other better way?

final String sPassword = null;

          do {
            Platform.runLater(new Runnable() {

                @Override
                public void run() {
                     sPassword = JavaFXDialog.showPasswordDialog(sTaskName + "Password", "Enter the password:", parentStage, 400.0, 160.0);
                }
            });

            if (sPassword == null) {
                System.out.println("Entering password cancelled.");
                throw new Exception("Cancel");
            }
        } while (sPassword.equalsIgnoreCase(""));

Solution 1:

I'd recommend wrapping the code within a FutureTask object. FutureTask is a construct useful (among other things) for executing a portion of code on one thread (usually a worker, in your case the event queue) and safely retrieving it on another. FutureTask#get will block until FutureTask#run has been invoked, therefore your password prompt could look like this:

final FutureTask query = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        return queryPassword();
    }
});
Platform.runLater(query);
System.out.println(query.get());

As FutureTask implements Runnable, you can pass it directly to Platform#runLater(...). queryPassword() will be inokved on the event queue, and the subsequent call to get block until that method completes. Of course, you will want to invoke this code in a loop until the password actually matches.

Solution 2:

Important

This code is for the specific case of when you have code which is not on the JavaFX application thread and you want to invoke code which is on the JavaFX application thread to display GUI to a user, then get a result from that GUI before continuing processing off the JavaFX application thread.

You must not be on the JavaFX application thread when you call CountdownLatch.await in the code snippet below. If you invoke CountDownLatch.await on the JavaFX Application thread, you will deadlock your application. Besides which, if you are already on the JavaFX application thread, you don't need to invoke Platform.runLater to execute something on the JavaFX application thread.

Most of the time you know if you are on the JavaFX application thread or not. If you are not sure, you can check your thread by calling Platform.isFxApplicationThread().


An alternate method using CountDownLatch. I like Sarcan's method better though ;-)

final CountDownLatch latch = new CountDownLatch(1);
final StringProperty passwordProperty = new SimpleStringProperty();
Platform.runLater(new Runnable() {
    @Override public void run() {
        passwordProperty.set(queryPassword());
        latch.countDown();
    }
});
latch.await();      
System.out.println(passwordProperty.get());

Here is some executable sample code demonstrating use of a CountdownLatch to suspend execution of a non-JavaFX application thread until a JavaFX dialog has retrieved a result which can then be accessed by the non-JavaFX application thread.

The application prevents the JavaFX launcher thread for the application from continuing until the user has entered the correct password in a JavaFX dialog. The access granted stage is not shown until the correct password has been entered.

enterpassword  access granted

import javafx.application.*;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.TextAlignment;
import javafx.stage.*;

import java.util.concurrent.CountDownLatch;

public class PasswordPrompter extends Application {
  final StringProperty passwordProperty = new SimpleStringProperty();
  @Override public void init() {
    final CountDownLatch latch = new CountDownLatch(1);

    Platform.runLater(new Runnable() {
      @Override public void run() {
        passwordProperty.set(new PasswordPrompt(null).getPassword());
        latch.countDown();
      }
    });

    try {
      latch.await();
    } catch (InterruptedException e) {
      Platform.exit();
    }

    System.out.println(passwordProperty.get());
  }

  @Override public void start(final Stage stage) {
    Label welcomeMessage = new Label("Access Granted\nwith password\n" + passwordProperty.get());
    welcomeMessage.setTextAlignment(TextAlignment.CENTER);

    StackPane layout = new StackPane();
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20px;");
    layout.getChildren().setAll(welcomeMessage);
    stage.setScene(new Scene(layout));

    stage.show();
  }

  public static void main(String[] args) { launch(args); }
}

class PasswordPrompt {
  final Window owner;

  PasswordPrompt(Window owner) {
    this.owner = owner;
  }

  public String getPassword() {
    final Stage dialog = new Stage();
    dialog.setTitle("Pass is sesame");
    dialog.initOwner(owner);
    dialog.initStyle(StageStyle.UTILITY);
    dialog.initModality(Modality.WINDOW_MODAL);
    dialog.setOnCloseRequest(new EventHandler<WindowEvent>() {
      @Override public void handle(WindowEvent windowEvent) {
        Platform.exit();
      }
    });

    final TextField textField = new TextField();
    textField.setPromptText("Enter sesame");
    final Button submitButton = new Button("Submit");
    submitButton.setDefaultButton(true);
    submitButton.setOnAction(new EventHandler<ActionEvent>() {
      @Override public void handle(ActionEvent t) {
        if ("sesame".equals(textField.getText())) {
          dialog.close();
        }
      }
    });

    final VBox layout = new VBox(10);
    layout.setAlignment(Pos.CENTER_RIGHT);
    layout.setStyle("-fx-background-color: azure; -fx-padding: 10;");
    layout.getChildren().setAll(textField, submitButton);

    dialog.setScene(new Scene(layout));
    dialog.showAndWait();

    return textField.getText();
  }
}

The above program prints password to the screen and console purely for demonstration purposes, displaying or logging passwords is not something you would do in a real application.