尝试在 JavaFX 中出现启动画面后切换阶段
Trying to switch stages after a splash screen in JavaFX
我试图显示启动画面,直到我加载所有必要的资源并打开主舞台,但我将 运行 保持为 InvocationTargetException
。
换句话说,我的初级阶段加载了一个 FXML,它有一个如下所示的控制器:
public class SplashController {
@FXML
VBox splashScreenVBox = new VBox();
@FXML
protected void initialize() throws InterruptedException, IOException {
Stage primaryStage = (Stage) splashScreenVBox.getScene().getWindow();
primaryStage.close();
new MainStage();
}
}
MainStage
class 正在加载 FXML 并显示场景:
public MainStage() throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/core/views/Main.fxml"));
this.setScene(new Scene(root, 800, 600));
this.show();
}
我得到的错误指向我第一次 FXMLLoader.load()
FXML 所在的行(我有两个 FXML 文件,每个阶段一个)。
有人可以解释一下为什么会发生这种情况,最好是如何正确使用 FXMLLoader
,以防我正在做的事情有问题吗?
编辑:堆栈跟踪
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication5(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:745)
Caused by: javafx.fxml.LoadException:
/C:/Users/REDACTED/out/production/REDACTED/core/views/Splash.fxml
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2571)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at core.Scenes.start(Scenes.java:19)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication12(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait5(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null3(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater4(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null8(WinApplication.java:191)
... 1 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2566)
... 17 more
Caused by: java.lang.NullPointerException
at core.controllers.SplashController.initialize(SplashController.java:18)
... 28 more
Exception running application core.Scenes
正如您从堆栈跟踪中看到的那样,您在第 18
行的 SplashController
的 initialize
方法中得到了一个 NullPointerException
。我假设第 18
行是这一行:
Stage primaryStage = (Stage) splashScreenVBox.getScene().getWindow();
这个问题很可能是这个电话:getScene().getWindow()
。方法 getScene()
将 return null
在这里,因为 splashScreenVBox
还不是 Scene
的一部分。怎么会这样? initialize
方法在 执行 FXMLLoader.load()
期间被调用 。这意味着您还没有机会将 FXMLLoader.load()
的结果添加到 Scene
。
要修复此选项,请向您的 SplashController
添加一个方法,该方法为 MainStage
.
加载
FXMLLoader loader = new FXMLLoader(getClass().getResource("your/resource"));
Parent root = loader.load();
Stage splashStage = new Stage();
splashStage.setScene(new Scene(root));
splashStage.show();
SplashController controller = loader.getController();
controller.loadMainApp(splashStage);
我将 splashStage
传递给方法,这样您就可以 hide/close 它准备好显示主要内容 Stage
。根据代码的设计,除了传递参数之外,可能还有其他方法可以做到这一点。
有关如何创建抽象控制器的示例,当根添加到 Scene
并且 Scene
已添加到 Window
时,该控制器将自动调用方法。
import java.util.function.Consumer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.stage.Window;
public abstract class AbstractController<T extends Node> {
// protected so subclasses can access the root
// directly. You could also hide this behind a
// getter.
@FXML protected T root;
// Subclasses that override this method must call the
// super implementation
@FXML
protected void initialize() {
Consumer<Window> onNewWindow = this::onAddedToWindow;
Consumer<Scene> onNewScene = scene ->
scene.windowProperty().addListener(new SelfRemovingChangeListener<>(onNewWindow));
root.sceneProperty().addListener(new SelfRemovingChangeListener<>(onNewScene));
}
protected abstract void onAddedToWindow(Window window);
private static class SelfRemovingChangeListener<T> implements ChangeListener<T> {
private final Consumer<? super T> onNewValue;
private SelfRemovingChangeListener(Consumer<? super T> onNewValue) {
this.onNewValue = onNewValue;
}
@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
onNewValue.accept(newValue);
observable.removeListener(this);
}
}
}
这要求您的 FXML 文件中有一个 fx:id="root"
,并且根对象是一个 Node
。 onAddedToWindow
仅在 第一次 时被调用 root
被添加到 Window
。您仍然需要将 root
添加到其他地方的 Window
(无论您在哪里调用 FXMLLoader.load
)。
我试图显示启动画面,直到我加载所有必要的资源并打开主舞台,但我将 运行 保持为 InvocationTargetException
。
换句话说,我的初级阶段加载了一个 FXML,它有一个如下所示的控制器:
public class SplashController {
@FXML
VBox splashScreenVBox = new VBox();
@FXML
protected void initialize() throws InterruptedException, IOException {
Stage primaryStage = (Stage) splashScreenVBox.getScene().getWindow();
primaryStage.close();
new MainStage();
}
}
MainStage
class 正在加载 FXML 并显示场景:
public MainStage() throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/core/views/Main.fxml"));
this.setScene(new Scene(root, 800, 600));
this.show();
}
我得到的错误指向我第一次 FXMLLoader.load()
FXML 所在的行(我有两个 FXML 文件,每个阶段一个)。
有人可以解释一下为什么会发生这种情况,最好是如何正确使用 FXMLLoader
,以防我正在做的事情有问题吗?
编辑:堆栈跟踪
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication5(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:745)
Caused by: javafx.fxml.LoadException:
/C:/Users/REDACTED/out/production/REDACTED/core/views/Splash.fxml
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2571)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at core.Scenes.start(Scenes.java:19)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication12(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait5(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null3(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater4(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null8(WinApplication.java:191)
... 1 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2566)
... 17 more
Caused by: java.lang.NullPointerException
at core.controllers.SplashController.initialize(SplashController.java:18)
... 28 more
Exception running application core.Scenes
正如您从堆栈跟踪中看到的那样,您在第 18
行的 SplashController
的 initialize
方法中得到了一个 NullPointerException
。我假设第 18
行是这一行:
Stage primaryStage = (Stage) splashScreenVBox.getScene().getWindow();
这个问题很可能是这个电话:getScene().getWindow()
。方法 getScene()
将 return null
在这里,因为 splashScreenVBox
还不是 Scene
的一部分。怎么会这样? initialize
方法在 执行 FXMLLoader.load()
期间被调用 。这意味着您还没有机会将 FXMLLoader.load()
的结果添加到 Scene
。
要修复此选项,请向您的 SplashController
添加一个方法,该方法为 MainStage
.
FXMLLoader loader = new FXMLLoader(getClass().getResource("your/resource"));
Parent root = loader.load();
Stage splashStage = new Stage();
splashStage.setScene(new Scene(root));
splashStage.show();
SplashController controller = loader.getController();
controller.loadMainApp(splashStage);
我将 splashStage
传递给方法,这样您就可以 hide/close 它准备好显示主要内容 Stage
。根据代码的设计,除了传递参数之外,可能还有其他方法可以做到这一点。
有关如何创建抽象控制器的示例,当根添加到 Scene
并且 Scene
已添加到 Window
时,该控制器将自动调用方法。
import java.util.function.Consumer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.stage.Window;
public abstract class AbstractController<T extends Node> {
// protected so subclasses can access the root
// directly. You could also hide this behind a
// getter.
@FXML protected T root;
// Subclasses that override this method must call the
// super implementation
@FXML
protected void initialize() {
Consumer<Window> onNewWindow = this::onAddedToWindow;
Consumer<Scene> onNewScene = scene ->
scene.windowProperty().addListener(new SelfRemovingChangeListener<>(onNewWindow));
root.sceneProperty().addListener(new SelfRemovingChangeListener<>(onNewScene));
}
protected abstract void onAddedToWindow(Window window);
private static class SelfRemovingChangeListener<T> implements ChangeListener<T> {
private final Consumer<? super T> onNewValue;
private SelfRemovingChangeListener(Consumer<? super T> onNewValue) {
this.onNewValue = onNewValue;
}
@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
onNewValue.accept(newValue);
observable.removeListener(this);
}
}
}
这要求您的 FXML 文件中有一个 fx:id="root"
,并且根对象是一个 Node
。 onAddedToWindow
仅在 第一次 时被调用 root
被添加到 Window
。您仍然需要将 root
添加到其他地方的 Window
(无论您在哪里调用 FXMLLoader.load
)。