【问题标题】:Return result from javafx platform runlater从 javafx 平台运行后返回结果
【发布时间】:2012-11-27 14:37:22
【问题描述】:

我正在开发 JavaFX 应用程序,在我的场景中是显示在 JavaFX 中创建的密码提示,它使用两个选项 OKCancel 输入密码。我已经返回了用户输入的密码。

我显示密码对话框的类是 -

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

我的代码在哪里显示密码提示如下,实际上这个提示会显示在其他 UI 上,所以我需要将它包含在 Platform.runlater() 中,否则它会抛出 Not on FX application thread。我需要显示此密码提示,直到我得到正确的提示。如果我在 runlater 中包含显示密码,如何获取密码值。

还有其他更好的方法吗?

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(""));

【问题讨论】:

    标签: javafx-2 javafx


    【解决方案1】:

    我建议将代码包装在 FutureTask 对象中。 FutureTask 是一种有用的构造(除其他外),用于在一个线程(通常是一个工作线程,在您的情况下为事件队列)上执行一部分代码并在另一个线程上安全地检索它。 FutureTask#get 将一直阻塞,直到调用 FutureTask#run,因此您的密码提示可能如下所示:

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

    由于FutureTask 实现了Runnable,您可以直接将其传递给Platform#runLater(...)queryPassword() 将在事件队列中被调用,随后调用 get 阻塞直到该方法完成。当然,您会希望循环调用此代码,直到密码实际匹配为止。

    【讨论】:

      【解决方案2】:

      重要

      此代码适用于 当您有代码不在 JavaFX 应用程序线程上的特定情况,并且您想调用 JavaFX 应用程序线程上的代码向用户显示 GUI,然后在继续处理 JavaFX 应用程序线程之前从该 GUI 获取结果。

      当您在下面的代码 sn-p 中调用 CountdownLatch.await 时,您不能在 JavaFX 应用程序线程上。如果您在 JavaFX 应用程序线程上调用 CountDownLatch.await,您将死锁您的应用程序。除此之外,如果您已经在 J​​avaFX 应用程序线程上,则无需调用 Platform.runLater 来在 JavaFX 应用程序线程上执行某些操作。

      大多数时候您都知道自己是否在 JavaFX 应用程序线程上。如果你不确定,你可以通过调用Platform.isFxApplicationThread()来检查你的线程。


      使用CountDownLatch 的替代方法。不过我更喜欢 Sarcan 的方法 ;-)

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

      这里是一些可执行示例代码,演示如何使用 CountdownLatch 来暂停非 JavaFX 应用程序线程的执行,直到 JavaFX 对话框检索到结果,然后非 JavaFX 应用程序线程可以访问该结果。

      在用户在 JavaFX 对话框中输入正确密码之前,应用程序会阻止应用程序的 JavaFX 启动器线程继续运行。在输入正确的密码之前,不会显示访问权限阶段。

        

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

      上述程序将密码打印到屏幕和控制台纯粹是出于演示目的,显示或记录密码不是您在真实应用程序中会做的事情。

      【讨论】:

      • 由于某种奇怪的原因,这不能按预期工作。它卡在 await 并且 Runnable.run 从未被调用过。
      • 它适用于我,我添加了一个可执行示例以进一步演示用法。
      • @xar 您可能在 JavaFX 应用程序线程上调用了latch.await(),这会使您的应用程序死锁。我在答案中添加了一些文字来解释。
      • 谢谢@jewelsea,现在说得通了。感谢您花时间解释 JavaFX 应用程序线程。
      猜你喜欢
      • 1970-01-01
      • 2013-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-12
      • 2014-07-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多