JDK11环境下向JDialog添加JFXPanel后对话框无内容显示问题
问题背景
- 程序基于Swing构建GUI,因需要使用CSS定制组件样式,采用
JFXPanel替代常规JPanel实现收集用户输入的模态对话框 - 运行环境为JDK 11
- 异常表现:
JFXPanel添加到JFrame时运行完全正常,但添加到JDialog时对话框无任何内容显示 - 复现规律:从纯Swing组件的窗口触发打开该对话框时功能正常,从嵌入了JavaFX内容的
JFXPanel所在窗口触发打开时故障复现
原实现代码
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Optional; import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.embed.swing.JFXPanel; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; public class JDialogExample { private Optional<String> output = Optional.empty(); private final StackPane stackPane = new StackPane(); private final JFXPanel panel = new JFXPanel(); private final JDialog dialog = getNewJDialog(); private final TextField tf = new TextField(); public JDialogExample() { ObservableList list = stackPane.getChildren(); list.addAll(new Label("My Label"), tf, getSubmitButton()); panel.setScene(new Scene(stackPane)); dialog.add(panel); dialog.pack(); dialog.setLocationRelativeTo(null); dialog.setVisible(true); } public Optional<String> show() { return output; } private Button getSubmitButton() { Button btn = new Button(); btn.setText("Submit"); btn.setOnAction(new EventHandler<>() { @Override public void handle(javafx.event.ActionEvent t) { output = Optional.of(tf.getText()); dialog.setVisible(false); } }); return btn; } protected JDialog getNewJDialog() { return new JDialog(new JFrame(), "My dialog", true); } public static void main(String[] args) { Platform.startup(() -> { JFrame frame1 = new JFrame(); JButton b = new JButton("This works"); b.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JDialogExample example = new JDialogExample(); Optional<String> userInputOptional = example.show(); if (userInputOptional.isPresent()) { System.out.println(userInputOptional.get()); } } }); frame1.add(b); frame1.pack(); frame1.setVisible(true); JFrame frame2 = new JFrame(); StackPane stackPane2 = new StackPane(); JFXPanel jFXPanel = new JFXPanel(); Button b2 = new Button("This DOES NOT work"); b2.setOnAction(new EventHandler<>() { @Override public void handle(javafx.event.ActionEvent t) { JDialogExample example = new JDialogExample(); Optional<String> userInputOptional = example.show(); if (userInputOptional.isPresent()) { System.out.println(userInputOptional.get()); } } }); ObservableList list2 = stackPane2.getChildren(); list2.addAll(b2); jFXPanel.setScene(new Scene(stackPane2)); frame2.add(jFXPanel); frame2.pack(); frame2.setLocationRelativeTo(null); frame2.setVisible(true); }); } }
故障根因
- 线程使用违规:Swing和JavaFX各自维护独立的UI事件线程,Swing组件的创建、修改、显示必须在Swing事件调度线程(EDT)执行,JavaFX组件、Scene的创建和修改必须在JavaFX Application Thread执行。原代码在JavaFX按钮的事件回调里直接创建、操作Swing的JDialog,跨线程操作UI会导致渲染初始化异常。
- 模态窗口阻塞事件循环:模态JDialog显示时会阻塞父窗口的事件分发,当触发源是嵌了JFXPanel的窗口时,JavaFX渲染线程拿不到JFXPanel初始化所需的调度资源,直接导致面板内容无法绘制。
- 尺寸计算错误:JFXPanel内容初始化存在延迟,
pack()执行时如果JFXPanel还没完成首帧渲染,会拿到0x0的preferredSize,导致对话框内容区域被压缩到不可见。
修复方式
- 严格遵守双UI线程规范:所有Swing操作通过
SwingUtilities.invokeLater()投递到EDT执行,所有JavaFX场景操作通过Platform.runLater()投递到JavaFX线程执行,禁止跨线程直接操作UI组件。 - 不要在JavaFX事件回调中直接弹出模态Swing对话框,回调中仅做线程投递,把对话框的创建、显示逻辑全部切到Swing EDT执行。
- 给承载内容的JFXPanel设置合理的初始首选尺寸,避免
pack()计算尺寸错误。
修复后核心逻辑示例
// JavaFX按钮点击回调中,切到Swing线程创建显示对话框 b2.setOnAction(t -> { SwingUtilities.invokeLater(() -> { JDialogExample example = new JDialogExample(); Optional<String> res = example.show(); res.ifPresent(System.out::println); }); }); // 构造方法中拆分线程操作 public JDialogExample() { dialog = getNewJDialog(); panel = new JFXPanel(); // 给JFXPanel设置初始尺寸 panel.setPreferredSize(new java.awt.Dimension(300, 150)); dialog.add(panel); // JavaFX内容初始化投递到FX线程 Platform.runLater(() -> { stackPane = new StackPane(); tf = new TextField(); stackPane.getChildren().addAll(new Label("My Label"), tf, getSubmitButton()); panel.setScene(new Scene(stackPane)); // 场景设置完成后再在Swing线程调整对话框显示 SwingUtilities.invokeLater(() -> { dialog.pack(); dialog.setLocationRelativeTo(null); dialog.setVisible(true); }); }); } // 提交按钮操作切回Swing线程关闭对话框 btn.setOnAction(t -> { output = Optional.of(tf.getText()); SwingUtilities.invokeLater(() -> dialog.setVisible(false)); }); // 主方法启动时,Swing窗口创建也要投递到EDT public static void main(String[] args) { Platform.startup(() -> { SwingUtilities.invokeLater(() -> { // 放frame1、frame2的创建显示逻辑 }); }); }
修复后无论从纯Swing窗口还是嵌了JFXPanel的窗口触发,模态对话框都可以正常显示JavaFX内容,不会再出现空白问题。
内容的提问来源于stack exchange,提问作者BabaNew
相关产品推荐
相关产品推荐

