通过将应用程序从Swing迁移到JavaFX,我遇到了问题。
为了使它尽可能简单,在我的应用程序开始时,JavaFX Stage
在主JavaFX类(Main.java)中初始化。在初始化结束时(在JFX线程中),我打开一个Swing customed JDialog
(此时未迁移),要求用户选择一个设备。在此JDialog
中,我有一个ObservableModel
,它在设备选择JDialog
中作为参数从用户发送。我的主要JavaFX类是Observer
,我通过覆盖update()
函数从所选设备获取条目。 到目前为止,一切正常。
然后,仍然在update()
函数中,如果用户未连接,我想打开一个我在JavaFX中创建的登录对话框(在迁移之前是一个Swing JDialog
)。当我尝试打开它时,我会收到一个java.lang.IllegalStateException
异常,告诉"Not on FX application thread; currentThread = AWT-EventQueue-0"
现在有些代码:
JavaFX Main
类Main.java:重要的是我通过调用JDialog
来打开openSlpDlg()
,它将要求用户选择一个设备,并且将返回选择的结果在update()
方法中作为参数
public class Main extends Application implements Observer {
@Override
public void start(Stage stage) {
// init stage
stage.setTitle(Messages.getString("Appli_HaslerST"));
stage.getIcons().add(new Image(getClass().getResourceAsStream("../gifs/application.gif")));
mainContainer = new BorderPane();
Scene root = new Scene(mainContainer, 1200, 1000);
stage.setScene(root);
// init stage content
initialize();
stage.show();
// open that device selection dialog
openSlpDlg();
this.stage = stage;
}
public static void main(String[] args) {
launch(args);
}
@Override
public void update(Observable o, Object arg) {
// called when user has chosen a device in the JDialog
if (arg instanceof Object[]) {
Object[] set = (Object[]) arg;
try {
// con is an Interface that will create the correct
// device JPanel, depending of selection.
// the JPanel is created in constructor of MainGUI
// Start method of device application which creates a
// new MainGUI (JPanel)
// AbstractTabbedPanel is a customed JPanel
AbstractTabbedPanel panel = con.getTabbedPanel();
} catch (Exception e) {
}
}
}
}
外部设备应用程序返回JPanel
,它将使用SwingNode
存储在我的舞台中(它可以正常工作)。但是在JPanel
创建此MainGUI
时(如上面代码中的注释中所提到的),我检查用户是否已登录,如果没有,则打开登录对话框,一个JavaFX Dialog<R>
MainGUI
。 con.getTabbedPane()
中的JavaFX主应用程序调用的update()
(在上面的代码中)将创建一个新的MainGUI
。
public class MainGUI extends JPanel {
public MainGUI() {
// [...] all initalization done in constructor
// test if user connected
if (LocalAccessControl.getInstance().getAccessControl() == null) {
// not connected case
// JavaFX custom login extends from Dialog<LoginInfo>
LoginDlg login;
login = new LoginDlg()
// show login dialog and get a LoginInfo object with
// user info
LoginInfo userInfo = login.showLogin();
}
}
}
并且LoginInfo userInfo = login.showLogin();
我得到了这个例外:java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0
问题是,我需要存储在userInfo
中的登录信息以继续在构造函数中构建我的JPanel
(所以在AWT EventQueue线程中)但我的LoginDlg是JavaFX实现的。我尝试使用Platform.runLater(new Runnable(){...})
,但它不起作用,这是正常的,因为我目前没有使用JavaFX线程,并且它不会立即在FX线程中执行(对话框永远不会显示,登录无法完成,所以JPanel将为null并显示一条警告消息,告知无法连接)。
我已经在stackoverflow上看到这个例外的帖子很少,但我认为我的情况有点棘手,我找不到一个很好的解决方案来克服这个问题。
希望你能帮助我。如果您需要更多详细信息,请询问!答案 0 :(得分:0)
混合Swing和JavaFX很棘手,因为每个工具包都是单线程的,并且每个工具包都有自己的UI线程,其中&#34; live&#34;必须管理UI元素。对于Swing,请参阅"Swing's Threading Policy" in the javax.swing package documentation;对于JavaFX,请参阅"Threading" section of the Application
documentation。如果违反它们,JavaFX会尽可能通过抛出运行时异常来强制执行这些规则(如您所见); Swing不会抛出异常。在任何一种情况下,违反线程规则都有可能使您的应用程序处于不确定状态,并在应用程序生命周期中的任意时间内导致错误。
您可以使用SwingUtilities.invokeLater(() -> { ... });
来调用AWT事件派发线程上的代码,使用Platform.runLater(() -> { ... });
来调用FX应用程序线程上的代码。这些调用是异步,因此它们不会立即执行;换句话说,紧跟在这些调用之后的代码很可能在代码提交给UI线程之前执行。
此外,您不应在UI线程上进行阻塞调用,除非明确设计这些阻塞调用。例如,如果dialog
是FX对话框(或阶段),dialog.showAndWait()
是一个阻塞调用,旨在安全地在FX Application线程上执行;但是你不应该在AWT事件派发线程上进行阻止来自FX对话框输入的调用。
您还没有在代码中展示完整的示例,但看起来您需要这样的内容:
@Override
public void start(Stage stage) {
// init stage
stage.setTitle(Messages.getString("Appli_HaslerST"));
stage.getIcons().add(new Image(getClass().getResourceAsStream("../gifs/application.gif")));
mainContainer = new BorderPane();
Scene root = new Scene(mainContainer, 1200, 1000);
stage.setScene(root);
// init stage content
initialize();
stage.show();
// open that device selection dialog: this is a Swing dialog,
// so it must be performed on the AWT event dispatch thread:
SwingUtilities.invokeLater(this::openSlpDlg);
this.stage = stage;
}
您的update()
方法是作为对AWT事件派发线程中用户输入的响应而执行的(来自openSlpDlg
所示的Swing对话框中的动作侦听器,我假设);因此它和它调用的方法,例如new MainGUI()
在AWT事件派发线程上执行。所以你现在需要:
public MainGUI() {
// this method is executed on the AWT event dispatch thread
// [...] all initalization done in constructor
// test if user connected
if (LocalAccessControl.getInstance().getAccessControl() == null) {
// not connected case
// JavaFX custom login extends from Dialog<LoginInfo>
// Tricky part. We need to create a JavaFX dialog (so must be
// done on the FX Application Thread), show it and wait for the result
// (so make a blocking call, which needs to be on a background thread),
// and process the result back on the AWT thread.
// task to execute on the FX Application Thread, returning a LoginInfo:
FutureTask<LoginInfo> getLoginTask = new FutureTask<>(() -> {
LoginDlg login;
login = new LoginDlg()
// show login dialog and get a LoginInfo object with
// user info
return login.showLogin();
});
// execute the task on the FX Application Thread:
Platform.runLater(getLoginTask);
// now create a background thread that waits for the login task to complete,
// and processes the result back on the AWT event dispatch thread:
Thread waitForLoginThread = new Thread(() -> {
try {
final LoginInfo userLogin = getLoginTask.get();
SwingUtilities.invokeLater(() -> {
// process userLogin here...
});
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption waiting for login");
} catch (ExecutionException exc) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Error getting login info", exc);
}
});
waitForLoginThread.start();
}
}
如果确实想要阻止MainGUI
构造函数,直到FX对话框被解除,您可以通过等待提交给FX Application Thread的任务直接在那里完成(而不是在后台线程中):
public MainGUI() {
// this method is executed on the AWT event dispatch thread
// [...] all initalization done in constructor
// test if user connected
if (LocalAccessControl.getInstance().getAccessControl() == null) {
// not connected case
// JavaFX custom login extends from Dialog<LoginInfo>
// Tricky part. We need to create a JavaFX dialog (so must be
// done on the FX Application Thread), show it and wait for the result
// (so make a blocking call, which needs to be on a background thread),
// and process the result back on the AWT thread.
// task to execute on the FX Application Thread, returning a LoginInfo:
FutureTask<LoginInfo> getLoginTask = new FutureTask<>(() -> {
LoginDlg login;
login = new LoginDlg()
// show login dialog and get a LoginInfo object with
// user info
return login.showLogin();
});
// execute the task on the FX Application Thread:
Platform.runLater(getLoginTask);
// wait for task submitted to FX Application Thread to complete.
// note this blocks the AWT event dispatch thread:
try {
final LoginInfo userLogin = getLoginTask.get();
// process userLogin here...
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} catch (ExecutionException exc) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Error getting login info", exc);
}
}
}
我不推荐这种方法:它会使Swing UI无响应,并可能导致糟糕的用户体验。构建应用程序通常更好,这样MainGUI
可以在没有登录完成的情况下显示,然后在登录完成时更新;或者在完成登录之前根本不调用MainGUI
构造函数。