向JavaFX应用程序传递参数
与Java应用程序一样,您可以将参数传递给JavaFX应用程序。以下有两种方式传递参数给JavaFX应用程序:
- 在独立应用程序的命令行中
- 在applet和WebStart应用程序的Java网络启动协议(JNLP)文件中。
Parameters类是Application类的静态内部类,它用来封装传递给JavaFX应用程序的参数。它将参数分为以下三种:
- 命名参数
- 未命名参数
- 原始参数(命名参数和未命名参数的组合)
需要使用Parameters类的以下三种方法来访问这三种类型的参数:
- Map getNamed()
- List getUnnamed()
- List getRaw()
参数可以是命名的也可以是未命名的。一个命名参数由一对(名称,值)组成。一个未命名的参数由单个值组成。getNamed()方法返回一个Map<String, String>,其中包含名称参数的键值对。getUnnamed()方法返回List<String>,其中每个元素都是一个未命名的参数值。
您只向JavaFX应用程序传递命名参数和未命名参数,不传递原始类型的参数。JavaFX运行时通过Parameters类的getRaw()方法使传递给应用程序的所有参数(命名的和未命名的)作为List<String>被返回。下面的讨论将清楚地说明这三个方法返回值之间的区别。
Application类的getParameters()方法返回Application.Parameters类的引用。对Parameters类的引用可以在Application类的init()方法和随后执行的代码中获得。参数在应用程序的构造函数中不可用,因为构造函数是在init()方法之前调用的。在构造函数中调用getParameters()方法将返回null。
表1-7中的程序读取传递给应用程序的所有类型的参数,并将它们显示在TextArea中。TextArea是一个显示多行文本的UI节点。
表1-7. 访问传递给JavaFX应用程序的参数
// FXParamApp.java
package com.jdojo.intro;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
public class FXParamApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage stage) {
// Get application parameters
Parameters p = this.getParameters();
Map<String, String> namedParams = p.getNamed();
List<String> unnamedParams = p.getUnnamed();
List<String> rawParams = p.getRaw();
String paramStr = "Named Parameters: " + namedParams + "\n" +
"Unnamed Parameters: " + unnamedParams + "\n" +
"Raw Parameters: " + rawParams;
TextArea ta = new TextArea(paramStr);
Group root = new Group(ta);
stage.setScene(new Scene(root));
stage.setTitle("Application Parameters");
stage.show();
}
}
让我们来看一些向FXParamApp类传递参数的情况。当您运行FXParamApp类时,在以下情况中,上述提及的输出被显示在有TextArea控件的窗口中。
案例一
如果这个类是作为一个独立的应用程序而运行的,请使用以下命令:
java com.jdojo.stage.FXParamApp Anna Lola
上面的命令传递了零个命名参数和两个未命名参数:Anna和Lola。原始参数列表将包含两个未命名参数。输出结果如下所示:
Named Parameters: {}
Unnamed Parameters: [Anna, Lola]
Raw Parameters: [Anna, Lola]
案例二
如果这个类是作为一个独立的应用程序而运行的,请使用以下命令:
java com.jdojo.stage.FXParamApp Anna Lola width=200 height=100
上面的命令不传递命名参数,尽管最后两个参数似乎是作为命名参数传递的。在命令行上对参数使用等号(=)不会使该参数成为命名参数。下一个例子解释了如何从命令行传递命名参数。
它传递了四个未命名的参数:Anna、Lola、width=200和height=100。原始参数列表将包含四个未命名参数。输出结果如下所示:
Named Parameters: {}
Unnamed Parameters: [Anna, Lola, width=200, height=100]
Raw Parameters: [Anna, Lola, width=200, height=100]
案例三
要从命令行传递一个命名参数,您需要在参数前面加上两个连字符(--)。也就是说,一个命名参数应该以如下形式输入:
--key=value
使用如下命令,这个类将作为一个独立的应用程序而运行:
java com.jdojo.stage.FXParamApp Anna Lola --width=200 --height=100
上面的命令传递两个命名参数:width=200和height=100。它传递了两个未命名参数:Anna和Lola。原始参数列表将包含四个元素:两个命名参数和两个未命名参数。原始参数列表中的命名参数值以两个连字符开头。输出结果如下所示:
Named Parameters: {height=100, width=200}
Unnamed Parameters: [Anna, Lola]
Raw Parameters: [Anna, Lola, --width=200, --height=100]
案例四
FXParamApp类是作为一个applet或WebStart应用程序而运行的。在这些情况下,有不同的方法来指定命名参数和未命名参数。但是,应用程序的内部会以相同的方式访问它们。注意,当使用getRaw()方法访问一个命名参数时,它前面有两个连字符。但是,在web和WebStart部署文件中指定命名参数时,不能在命名参数前加两个连字符。
使用WebStart启动FXParamApp应用程序的JNLP文件的部分内容如下所示。它指定了两个命名参数和两个未命名参数:
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0" xmlns:jfx="http://" href="FX_NetBeans_Only.jnlp">
...
<jfx:javafx-desc ... >
<fx:param name="width" value="200"/>
<fx:param name="height" value="100"/>
<fx:argument>Anna</fx:argument>
<fx:argument>Lola</fx:argument>
</jfx:javafx-desc>
</jnlp>
启动JavaFX应用程序
在前面,我谈到了在开发JavaFX第一个应用程序时启动JavaFX应用程序的主题。本节将提供更多关于启动JavaFX应用程序的细节。
每个JavaFX应用程序类都继承自Application类。Application类在javafx.application包中。它包含一个静态的launch()方法。它的唯一目的是启动一个JavaFX应用程序。它是一个重载的方法,有以下两种变体:
- static void launch(Class<? extends Application> appClass, String... args)
- static void launch(String... args)
注意,您没有创建JavaFX应用程序类的对象去启动它。当调用launch()方法时,JavaFX在运行时会创建应用程序类的一个对象。
JavaFX应用程序类必须有一个无参数的构造函数,否则当试图启动它时将引发运行时异常。
launch()方法的第一个变体很清晰。您将应用程序类的类引用作为第一个参数传递,launch()方法将创建该类的一个对象。第二个参数由传递给应用程序的命令行参数组成。下面的代码片段展示了如何使用launch()方法的第一个变体:
public class MyJavaFXApp extends Application {
public static void main(String[] args) {
Application.launch(MyJavaFXApp.class, args);
}
// More code goes here
}
传递给launch()方法的类引用不必与调用该方法的类相同。例如,下面的代码片段从MyAppLauncher类启动了MyJavaFXApp应用程序类,它没有扩展Application类:
public class MyAppLauncher {
public static void main(String[] args) {
Application.launch(MyJavaFXApp.class, args);
}
// More code goes here
}
launch()方法的第二个变体只接受一个参数,即传递给应用程序的命令行参数。它使用哪个JavaFX应用程序类去启动应用程序?它尝试根据调用者查找应用程序类名。它检查调用它的代码的类名。如果调用了该方法的代码所在的类直接或间接的继承了Application类,则将使用该类启动JavaFX应用程序。否则,将抛出运行时异常。让我们看一些例子来明确这条规则。
在下面的代码片段中,launch()方法检测到它是从MyJavaFXApp类的main()方法调用的。MyJavaFXApp类继承自Application类。因此,MyJavaFXApp类被用作应用程序类:
public class MyJavaFXApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
// More code goes here
}
在下面的代码片段中,Test类的main()方法调用了launch()方法。因为Test类没有继承Application类,所以,抛出一个运行时异常,如下面的代码输出所示:
public class Test {
public static void main(String[] args) {
Application.launch(args);
}
// More code goes here
}
Exception in thread "main" java.lang.RuntimeException: Error: class Test is not a subclass
of javafx.application.Application
at javafx.application.Application.launch(Application.java:211)
at Test.main(Test.java)
在下面的代码片段中,launch()方法检测到它是被MyJavaFXApp$1类的run()方法调用的。注意,MyJavaFXApp$1类是编译器生成的一个匿名内部类,它是Object类的子类,而不是Application类的子类,并且它实现了Runnable接口。因为对launch()方法的调用是在MyJavaFXApp$1类中,而这个类不是Application类的子类,所以抛出了一个运行时异常,如下面的代码输出所示:
public class MyJavaFXApp extends Application {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
public void run() {
Application.launch(args);
}
});
t.start();
}
// More code goes here
}
Exception in thread "Thread-0" java.lang.RuntimeException: Error: class MyJavaFXApp$1 is
not a subclass of javafx.application.Application
at javafx.application.Application.launch(Application.java:211)
at MyJavaFXApp$1.run(MyJavaFXApp.java)
at java.lang.Thread.run(Thread.java:722)
现在你知道了如何启动JavaFX应用程序,是时候去学习启动JavaFX应用程序的最佳实践了:将main()方法中的代码限制为一句话来启动应用程序,如下所示:
public class MyJavaFXApp extends Application {
public static void main(String[] args) {
Application.launch(args);
// Do not add any more code in this method
}
// More code goes here
}
Application类的launch()方法只能被调用一次,否则将引发运行时异常。对launch()方法的调用会阻塞,直到应用程序终止。并不总是需要main()方法来启动JavaFX应用程序。JavaFX打包程序会为您合成一个。例如,当您使用NetBeans IDE时,您不需要有main()方法,如果有,NetBeans会忽略它。
JavaFX应用程序的生命周期
JavaFX运行时创建了几个线程。在应用程序的不同阶段,线程用于执行不同的任务。在本节中,我将只解释那些在Application类的生命周期中被用来调用Application类的方法的线程。JavaFX运行时在其他线程中创建了两个线程:
- JavaFX-Launcher
- JavaFX Application Thread
Application类的launch()方法创建了这些线程。在JavaFX应用程序的生命周期内,JavaFX运行时按顺序调用指定的JavaFX应用程序类的以下方法:
- The no-args constructor
- The init() method
- The start() method
- The stop() method
JavaFX运行时在JavaFX应用程序线程上创建一个指定的应用程序类的对象。JavaFX Launcher线程调用指定的Application类的init()方法。Application类中的init()方法是空的,您可以在应用程序类中重写此方法。在JavaFX Launcher线程上创建Stage或Scene是不被允许的。它们必须在JavaFX应用程序线程中创建。因此,您不能在init()方法中创建Stage或Scene。尝试这样做会引发运行时异常。但init()方法可以创建UI控件,例如按钮或形状。
JavaFX应用程序线程调用指定的应用程序类的start(Stage Stage)方法。注意,Application类中的start()方法被声明为抽象的,您必须在应用程序类中重写此方法。
此时,launch()方法等待JavaFX应用程序完成。当应用程序完成时,JavaFX应用程序线程调用指定的应用程序类的stop()方法。在Application类中,stop()方法的默认实现是空的。您必须在应用程序类中重写此方法,以便在应用程序停止时执行您的逻辑。
表1-8中的代码说明了JavaFX应用程序的生命周期。它显示的是一个空舞台。在显示舞台时,您将看到输出的前三行。您需要关闭舞台才能看到输出的最后一行。
表1-8. JavaFX应用程序的生命周期
// FXLifeCycleApp.java
package com.jdojo.intro;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class FXLifeCycleApp extends Application {
public FXLifeCycleApp() {
String name = Thread.currentThread().getName();
System.out.println("FXLifeCycleApp() constructor: " + name);
}
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void init() {
String name = Thread.currentThread().getName();
System.out.println("init() method: " + name);
}
@Override
public void start(Stage stage) {
String name = Thread.currentThread().getName();
System.out.println("start() method: " + name);
Scene scene = new Scene(new Group(), 200, 200);
stage.setScene(scene);
stage.setTitle("JavaFX Application Life Cycle");
stage.show();
}
@Override
public void stop() {
String name = Thread.currentThread().getName();
System.out.println("stop() method: " + name);
}
}
FXLifeCycleApp() constructor: JavaFX Application Thread
init() method: JavaFX-Launcher
start() method: JavaFX Application Thread
stop() method: JavaFX Application Thread
终止JavaFX应用程序
JavaFX应用程序可以显式终止或者隐式终止。您可以通过调用Platform.exit()方法显式终止JavaFX应用程序。当调用此方法时,在start()方法之后或从start()方法内部调用Application类的stop()方法,然后终止JavaFX应用程序线程。此时,如果只有守护线程在运行,那么JVM将退出。如果从Application类的构造函数或init()方法调用Platform.exit()方法,则可能不会调用stop()方法。
JavaFX应用程序可以在web浏览器中运行。在web环境中调用Platform.exit()方法可能没有任何效果。
当最后一个窗口关闭时,JavaFX应用程序可能隐式终止。可以使用Platform类的静态方法setImplicitExit(boolean implicitExit)来打开或关闭此行为。将true传入此方法会隐式终止JavaFX应用程序,将false传递给此方法则不会终止。默认情况下,该行为是打开的。这就是为什么到目前为止,在大多数示例中,当您关闭窗口时,应用程序会被终止。当这个行为被打开时,Application类的stop()方法会在终止JavaFX Application Thread之前被调用。终止JavaFX应用程序线程并不总是终止JVM。如果所有运行的非守护线程都终止,则JVM终止。如果关闭了JavaFX应用程序的隐式终止行为,则必须调用Platform类的exit()方法来终止应用程序。
总结
JavaFX是一个基于java的开源的GUI框架,用于开发富客户端应用程序。它是在Java平台上GUI开发技术领域中Swing的继承者。
JavaFX中的GUI被显示在一个舞台中。舞台是Stage类的一个实例。舞台是桌面应用程序中的窗口和web应用程序中的浏览器区域。一个舞台包含一个场景。一个场景包含一组以树状结构排列的节点(图形)。
JavaFX应用程序继承了Application类。JavaFX运行时创建了被称为初始舞台的第一个舞台,并调用了应用程序类中的start()方法,传递初始舞台的引用。开发人员需要向舞台添加一个场景,并使舞台在start()方法中可见。
您可以使用Application类的launch()方法启动JavaFX应用程序。如果您运行继承了应用程序类的Java类(即JavaFX应用程序类),则Java命令会自动为您启动JavaFX应用程序。
在JavaFX应用程序的生命周期内,JavaFX运行时以特定的顺序调用JavaFX application类的预定义方法。首先,调用该类的无参构造函数,然后调用init()方法和start()方法。当应用程序终止时,stop()方法被调用。
可以通过调用Platform.exit()方法来终止JavaFX应用程序。当应用程序作为applet在web浏览器中运行时,调用Platform.exit()方法可能不会产生任何效果。
下一章将介绍JavaFX中的属性和绑定。