3 将JavaFX集成到Swing应用程序中
本章介绍如何将JavaFX内容添加到Swing应用程序中,以及在单个应用程序中同时运行Swing和JavaFX内容时如何正确使用线程。
JavaFX SDK提供了位于javafx.embed.swing
包中的JFXPanel
类,它使您可以将JavaFX内容嵌入到Swing应用程序中。
将JavaFX内容添加到Swing组件中
为了本章的目的,您将创建一个JFrame
组件,向其添加一个JFXPanel
对象,并设置包含JavaFX内容的JFXPanel组件的图形场景。
与任何Swing应用程序一样,您需要在事件分派线程(EDT)上创建图形用户界面(GUI)。示例3-1显示了initAndShowGUI
方法,该方法创建一个JFrame
组件并向其添加一个JFXPanel
对象。创建JFXPanel
类的实例隐式启动了JavaFX运行时。创建GUI后,调用initFX
方法在JavaFX应用程序线程上创建JavaFX场景。
示例3-1
import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class Test { private static void initAndShowGUI() { // 此方法在EDT线程上调用 JFrame frame = new JFrame("Swing和JavaFX"); final JFXPanel fxPanel = new JFXPanel(); frame.add(fxPanel); frame.setSize(300, 200); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Platform.runLater(new Runnable() { @Override public void run() { initFX(fxPanel); } }); } private static void initFX(JFXPanel fxPanel) { // 此方法在JavaFX线程上调用 Scene scene = createScene(); fxPanel.setScene(scene); } private static Scene createScene() { Group root = new Group(); Scene scene = new Scene(root, Color.ALICEBLUE); Text text = new Text(); text.setX(40); text.setY(100); text.setFont(new Font(25)); text.setText("欢迎使用JavaFX!"); root.getChildren().add(text); return (scene); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { initAndShowGUI(); } }); } }
Swing-JavaFX互操作性和线程
在单个应用程序中同时存在JavaFX和Swing数据时,可能会遇到以下互操作性情况:
-
通过更改Swing数据触发JavaFX数据的更改。
-
通过更改JavaFX数据触发Swing数据的更改。
响应Swing数据更改而更改JavaFX数据
JavaFX数据应仅在JavaFX用户线程上访问。每当您必须更改JavaFX数据时,将您的代码封装到一个Runnable对象中,并调用Platform.runLater
方法,如示例3-2所示。
响应JavaFX数据更改而更改Swing数据
Swing数据应仅在EDT上更改。为确保您的代码在EDT上执行,将其封装到一个Runnable对象中,并调用SwingUtilities.invokeLater
方法,如示例3-3所示。
介绍SimpleSwingBrowser应用程序
为了了解Swing-JavaFX互操作性的工作原理,可以考虑使用SimpleSwingBrowser
应用程序。这是一个带有集成JavaFX组件的Swing应用程序,用于查看网页。您可以在地址栏中输入URL并在应用程序窗口中查看加载的页面。 SimpleSwingBrowser
应用程序窗口如图3-1所示。
初始化Swing数据
您可以查看SimpleSwingBrowser.java
文件,或者下载包含NetBeans项目的SimpleSwingBrowser.zip
文件。从zip文件中提取文件到本地文件系统上的一个目录,并在Netbeans IDE中运行该项目。
从版本7.2开始,NetBeans IDE支持嵌入JavaFX内容的Swing应用程序。创建新项目时,在JavaFX类别中选择Swing应用程序中的JavaFX。
注意:
要在防火墙后运行此应用程序,必须指定代理设置,以便应用程序可以访问远程资源。
在NetBeans IDE中,右键单击项目窗口中的SimpleSwingBrowser项目,选择属性,在项目属性对话框中选择运行。
在VM选项字段中,以以下格式设置代理:
-Dhttp.proxyHost=webcache.mydomain.com -Dhttp.proxyPort=8080
SimpleSwingBrowser
应用程序的GUI在应用程序启动时在EDT上创建。 main
方法的实现如示例3-4所示。
示例3-4
public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { SimpleSwingBrowser browser = new SimpleSwingBrowser(); browser.setVisible(true); browser.loadURL("http://oracle.com"); } }); }
SimpleSwingBrowser
类初始化Swing对象,并调用initComponents
方法创建GUI,如示例3-5所示。
示例 3-5
public class SimpleSwingBrowser extends JFrame { private final JFXPanel jfxPanel = new JFXPanel(); private WebEngine engine; private final JPanel panel = new JPanel(new BorderLayout()); private final JLabel lblStatus = new JLabel(); private final JButton btnGo = new JButton("前往"); private final JTextField txtURL = new JTextField(); private final JProgressBar progressBar = new JProgressBar(); public SimpleSwingBrowser() { super(); initComponents(); } private void initComponents() { createScene(); ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { loadURL(txtURL.getText()); } }; btnGo.addActionListener(al); txtURL.addActionListener(al); progressBar.setPreferredSize(new Dimension(150, 18)); progressBar.setStringPainted(true); JPanel topBar = new JPanel(new BorderLayout(5, 0)); topBar.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5)); topBar.add(txtURL, BorderLayout.CENTER); topBar.add(btnGo, BorderLayout.EAST); JPanel statusBar = new JPanel(new BorderLayout(5, 0)); statusBar.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5)); statusBar.add(lblStatus, BorderLayout.CENTER); statusBar.add(progressBar, BorderLayout.EAST); panel.add(topBar, BorderLayout.NORTH); panel.add(jfxPanel, BorderLayout.CENTER); panel.add(statusBar, BorderLayout.SOUTH); getContentPane().add(panel); setPreferredSize(new Dimension(1024, 600)); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); } }
该应用程序的顶层窗口是一个 JFrame 对象,其中包含各种 Swing 组件,如文本字段、按钮、进度条和用于显示 JavaFX 内容的 JFX 面板。
加载JavaFX内容
在第一次运行时,将http://oracle.com网页加载到WebView对象中。当在地址栏中输入新的URL时,动作监听器(附加在initComponents方法中的txtURL文本字段上)会启动页面加载,如示例3-6所示。
示例3-6
ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { loadURL(txtURL.getText()); } };
JavaFX数据只能在JavaFX应用程序线程上访问。loadURL方法将代码封装到一个Runnable对象中,并调用Platform.runLater方法,如示例3-7所示。
示例3-7
public void loadURL(final String url) { Platform.runLater(new Runnable() { @Override public void run() { String tmp = toURL(url); if (url == null) { tmp = toURL("http://" + url); } engine.load(tmp); } }); } private static String toURL(String str) { try { return new URL(str).toExternalForm(); } catch (MalformedURLException exception) { return null; } }
更新Swing数据
当将新页面加载到WebView组件中时,从JavaFX数据中检索页面的标题,并将其传递给Swing GUI,以作为标题放置在应用程序窗口上。这个行为在createScene方法中实现,如示例3-8所示。
示例3-8
private void createScene() { Platform.runLater(new Runnable() { @Override public void run() { WebView view = new WebView(); engine = view.getEngine(); engine.titleProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, final String newValue) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { SimpleSwingBrowser.this.setTitle(newValue); } }); } }); } }); }