我有一个示例javafx应用程序,它在其中嵌入了一个Swing应用程序。在下面的示例应用程序中,我有一个链接,我需要启动一个 AWT 窗口。我使用一种方法getRootPane获取AWT窗口的JrootPane,该方法需要相当长的时间才能启动。因此,我使用非FX线程来调用launchWindow。
我希望一旦我单击链接,链接就会被禁用,一旦我关闭新启动的窗口,链接就会再次启用。
但是在我下面的示例代码中,在启动子窗口之前,链接不会禁用。请帮忙为什么会这样。我该怎么做才能解决这个问题
public class GifViewerTest extends Application
{
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage stage) throws Exception
{
VBox box = new VBox();
Image icon = new Image(this.getClass().getResource("/images/images.png").toExternalForm());
ImageView imageView = new ImageView(icon);
box.getChildren().add(imageView);
Hyperlink link = new Hyperlink("Test Link", imageView);
box.getChildren().add(link);
Button localLoadButton = new Button("Local load");
link.setOnMousePressed(e ->
{
link.setDisable(true);
// separate non-FX thread
new Thread() {
// runnable for that thread
public void run() {
Platform.runLater(new Runnable() {
public void run() {
launchWindow(link,400, 400);
}
});
}
}.start();
});
stage.setScene(new Scene(box, 500, 400));
stage.show();
}
JRootPane getRootPane()
{
int j = 0;
for (int i = 500000; i > 0; i--)
System.out.println(i);
;
return new JRootPane();
}
private void launchWindow(Hyperlink wcaLink,int stageWidth, int stageHeight)
{
Stage stage = new Stage();
JRootPane rootPane = getRootPane();
rootPane.setName("Sample Swing App");
final SwingNode swingNode = new SwingNode();
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
swingNode.setContent(rootPane);
}
});
AnchorPane anchorPane = new AnchorPane();
// add the following code to make sure the swing node grows with the window.
AnchorPane.setTopAnchor(swingNode, 0d);
AnchorPane.setBottomAnchor(swingNode, 0d);
AnchorPane.setRightAnchor(swingNode, 0d);
AnchorPane.setLeftAnchor(swingNode, 0d);
anchorPane.getChildren().add(swingNode);
stage.setTitle("Sample JavaFx Embedding Swing");
stage.setScene(new Scene(anchorPane, stageWidth, stageHeight));
stage.show();
stage.setOnCloseRequest((e) ->{
wcaLink.setDisable(false);
System.out.println("Enabling wcaLink again");
});
}
}
UI 开发的基本线程规则是:
- 切勿在 UI 线程上运行长时间运行的代码。这将使 UI 无响应,并阻止 UI 更新及时发生。
- 更新 UI 的任何代码都必须在 UI 线程上运行。这是因为 UI 工具包通常是单线程的,因此对 UI 元素的所有访问都必须在同一线程上进行。
请注意,为了实现此目的,必须将任何非 UI 代码(可能长时间运行(与 UI 代码分开。在任何情况下,这都是很好的做法,无需考虑线程,因为它符合关注点分离和 MVC 类型设计等一般原则。
由于您混合了两个 UI 工具包(JavaFX 和 Swing(,因此您需要应用规则 2 两次,每个工具包一次。必须在 AWT 事件调度线程上创建和访问 Swing 组件,并且必须在 FX 应用程序线程上创建和访问 JavaFX 节点。
你的代码基本上以错误的方式做所有事情。您创建一个线程,其唯一的执行语句是Platform.runLater()
。此方法只是计划在 FX 应用程序线程上执行Runnable
并立即退出。由于这基本上不需要时间来运行,因此为其创建后台线程没有任何目的。
另一方面,您计划在 FX 应用程序线程上执行的Runnable
会调用launchWindow()
,而又会调用getRootPane()
,这是一个长时间运行的方法。因此,您将阻止 FX 应用程序线程,并阻止处理对 UI 的任何修改(包括禁用链接(,直到该长时间运行的方法完成。
理论上,您应该:
- 在后台线程上执行长时间运行的方法
- 在 AWT 事件调度线程上创建
JRootPane
和其他 Swing 组件 - 创建
SwingNode
并在 FX 应用程序线程上执行其他 JavaFX UI 工作。
从注释中,您指出不可能将长时间运行的过程与JRootPane
的创建分开,因为该代码属于无法分离这些问题的第三方遗留应用程序。
这个问题的正确答案是要求遗留应用程序的维护者修复其代码中固有的线程问题。如果该代码的作者真的像您声称的那样犯了这些基本和根本的错误,那么您应该强烈考虑不使用该代码库(您预计还有多少其他错误?(并寻找另一种解决方案。我当然不会碰任何我知道写得像你声称的那样糟糕的产品。
如果您由于某种原因被迫使用该第三方应用程序,则最好的选择可能是在后台线程上创建JRootPane
。这很可能有效,尽管它不能保证,并且可能会在任意时间失败并在任意系统/JVM 上中断,因为 Swing 在某种程度上可以防止此类线程规则违规。
这里的策略是:
启动一个后台线程,该线程通过长时间运行的进程创建JRootPane
,然后使用SwingUtilities.runLater()
完成代码的 Swing 部分。
下面是使用此方法的代码版本(已大量清理(:
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class GifViewerTest extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
VBox box = new VBox();
Hyperlink link = new Hyperlink("Test Link");
box.getChildren().add(link);
link.setOnAction(e -> {
link.setDisable(true);
launchWindow(link, 400, 400);
});
stage.setScene(new Scene(box, 500, 400));
stage.show();
}
private JRootPane getRootPane() {
// simulate long-running process:
try {
Thread.sleep(5000);
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
}
return new JRootPane();
}
private void launchWindow(Hyperlink wcaLink, int stageWidth, int stageHeight) {
Stage stage = new Stage();
final SwingNode swingNode = new SwingNode();
Thread thread = new Thread(() -> {
JRootPane rootPane = getRootPane();
System.out.println("Load complete");
SwingUtilities.invokeLater(() -> {
rootPane.setName("Sample Swing App");
rootPane.getContentPane().add(new javax.swing.JLabel("Root pane loaded"));
swingNode.setContent(rootPane));
});
});
thread.start();
AnchorPane anchorPane = new AnchorPane();
// add the following code to make sure the swing node grows with the window.
AnchorPane.setTopAnchor(swingNode, 0d);
AnchorPane.setBottomAnchor(swingNode, 0d);
AnchorPane.setRightAnchor(swingNode, 0d);
AnchorPane.setLeftAnchor(swingNode, 0d);
anchorPane.getChildren().add(swingNode);
stage.setTitle("Sample JavaFx Embedding Swing");
stage.setScene(new Scene(anchorPane, stageWidth, stageHeight));
stage.setOnCloseRequest((e) -> {
wcaLink.setDisable(false);
System.out.println("Enabling wcaLink again");
});
stage.show();
}
}
请注意,这会立即显示窗口,然后在对getRootPane()
的调用完成时更新该窗口。如果希望窗口在getRootPane()
完成之前不显示,则可以将剩余的 JavaFX 代码包装在Platform.runLater()
中,您可以从 AWT 事件调度线程调用该代码:
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class GifViewerTest extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
VBox box = new VBox();
Hyperlink link = new Hyperlink("Test Link");
box.getChildren().add(link);
link.setOnAction(e -> {
link.setDisable(true);
launchWindow(link, 400, 400);
});
stage.setScene(new Scene(box, 500, 400));
stage.show();
}
private JRootPane getRootPane() {
// simulate long-running process:
try {
Thread.sleep(5000);
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
}
return new JRootPane();
}
private void launchWindow(Hyperlink wcaLink, int stageWidth, int stageHeight) {
final SwingNode swingNode = new SwingNode();
Thread thread = new Thread(() -> {
JRootPane rootPane = getRootPane();
System.out.println("Load complete");
SwingUtilities.invokeLater(() -> {
rootPane.setName("Sample Swing App");
rootPane.getContentPane().add(new javax.swing.JLabel("Root pane loaded"));
swingNode.setContent(rootPane);
Platform.runLater(() -> {
Stage stage = new Stage();
AnchorPane anchorPane = new AnchorPane();
// add the following code to make sure the swing node grows with the window.
AnchorPane.setTopAnchor(swingNode, 0d);
AnchorPane.setBottomAnchor(swingNode, 0d);
AnchorPane.setRightAnchor(swingNode, 0d);
AnchorPane.setLeftAnchor(swingNode, 0d);
anchorPane.getChildren().add(swingNode);
stage.setTitle("Sample JavaFx Embedding Swing");
stage.setScene(new Scene(anchorPane, stageWidth, stageHeight));
stage.setOnCloseRequest((e) -> {
wcaLink.setDisable(false);
System.out.println("Enabling wcaLink again");
});
stage.show();
});
});
});
thread.start();
}
}