从具有加载的FXML内容的对话框启动线程时出现“Not on FX application thread”错误。

4
我目前的情况如下:
我创建了一个JavaFX应用程序,其中包含一个屏幕,我可以通过点击屏幕上的按钮打开一个对话框。然后,用户提供输入并单击应用按钮。然后,用户输入将被发送到一个方法,该方法会打开某种进度对话框(用于向用户显示同步状态,这对问题不重要)。我将称此对话框为'MyDialog'。MyDialog使用以下代码构建:
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);

这很好用。在MyDialog中显示的ProgressBar指示了一个任务的进度。该任务是从一个线程启动的。该线程是从上屏控制器启动的。在任务内部,我希望在某个时刻显示一个额外的对话框,以获取一些用户验证。我将称此对话框为'AlertDialog'。以下是该部分的代码(位于上屏控制器而非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
        }
    }
}

很遗憾,AlertDialog没有显示出来,我收到了以下错误信息:

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

我已经尝试了以下解决方案:

  • 将AlertDialog代码放入MyDialogController中,并从任务中调用它。
  • 从MyDialogController启动线程,而不是从上层屏幕控制器启动,然后通过'childController.thread'调用它。

这两种解决方案都没有起作用。我认为这可能与在DialogPane中加载FXML文件有关,因此与线程以及从哪里启动有关。

因此,在这种情况下,问题如下:

  1. 为什么会出现此错误?

  2. 错误是否与AlertDialog未显示有关?

  3. 这部分代码的处理方式是否应该不同?(例如:在对话框中不加载外部FXML文件)

非常感谢您的帮助!

1个回答

6
我感觉在SO上已经写了100遍了..再次强调: JavaFX是一个单线程的GUI工具包,因此所有与GUI相关的操作都必须在主JavaFX线程上执行。
如果你试图在JavaFX线程之外做一些与GUI相关的操作,你将会得到一个`IllegalStateException: Not on FX application thread`异常。
`Optional result = alert.showAndWait();`是一个GUI操作,因为你初始化并显示了一个对话框(Dialog),所以你应该在其他地方检索用户输入,并且只在后台进行长时间运行的计算。
检索用户输入的好方法可以使用`Task`类的各种生命周期钩子(例如`succeeded()`或`failed()`)。

因为你正在从任务中调用 Alert alert = new Alert(...);,而你所说的任务是在一个 Thread 中执行的。如果你需要暂停后台线程以等待用户输入,请使用这种技术 - James_D
好的,谢谢@James_D,那将解决这个问题。只是,在切换第一个和第二个对话框(两个自定义对话框)之间出现了错误。那么这该怎么解释呢? - bashoogzaad
1
在您发布的代码中,您明确在后台线程上调用 new Alert(...) ,这清楚地解释了为什么会出现错误。如果您有其他代码认为正在FX应用程序线程上执行并导致错误,请使用该代码更新您的问题(并显示堆栈跟踪并确定在堆栈跟踪中导致错误的代码行)。 - James_D
好的@James_D,还有一个问题:我之前在另一个对话框中已经创建了一个任务和线程,在这里它可以正常工作(没有使用 Platform.runLater())。所以我看了一下区别,发现问题是Task protectedTask public之间的区别。当public时,我可以简单地设置一个标签而不会出错。那么这可能就是整个问题的原因吗? - bashoogzaad
好的,感谢eckig和James_D的帮助。我按照James_D的建议,在runLater中使用了FutureTask来解决问题。 - bashoogzaad
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接