您如何在创建的原始 window 中的控制器之间转换
How do you transition between controllers within the original window that was created
我目前有 3 个 classes。
ScreenController(控制器class):
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;
public class ScreenController implements Initializable
{
private AnchorPane window;
public ScreenController()
{
super();
}
public ScreenController(AnchorPane window)
{
setWindow(window);
}
public void setWindow(AnchorPane window)
{
this.window = window;
}
public void setScreen(String screen)
{
try
{
Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
window.getChildren().setAll(root);
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Override
public void initialize(URL location, ResourceBundle resources)
{
}
}
LoginScreen(主屏幕):
import com.app.client.java.controllers.ScreenController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import java.io.IOException;
public class LoginScreen extends ScreenController
{
@FXML
private AnchorPane loginWindow;
@FXML
private Button goButton;
public LoginScreen()
{
super();
setWindow(loginWindow);
}
@FXML
public void goButtonPressed(ActionEvent event) throws IOException
{
setScreen("Home");
System.out.println("Success.");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="loginWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" opacity="0.5" prefHeight="500.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.LoginScreen">
<children>
<Button fx:id="goButton" layoutX="205.0" layoutY="60.0" mnemonicParsing="false" onAction="#goButtonPressed" text="Button" />
</children>
</AnchorPane>
主屏幕(副屏):
import com.app.client.java.controllers.ScreenController;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
public class HomeScreen extends ScreenController
{
@FXML
private static AnchorPane homeWindow = new AnchorPane();
public HomeScreen()
{
super (homeWindow);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="homeWindow" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.HomeScreen">
<children>
<TextArea layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0" text="aksajkasjkasja" />
</children>
</AnchorPane>
我希望能够使用 setScreen() 函数从主屏幕移动到辅助屏幕。但是,我发现该过程没有成功完成。
我发现另一种可行的方法是(虽然它调整了 window 的大小,而不是用新的内容填充初始 window):
Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
Stage stage = (Stage) loginWindow.getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
但是,我更愿意使用初始实现,因为它更简洁、可读性更强,而且从理论上讲,它提供了我想要的确切行为。
您目前拥有的有几个问题:
在您的 LoginScreen
构造函数中,您使用尚未注入的字段的值调用 setWindow
:
public LoginScreen()
{
super();
setWindow(loginWindow);
}
当控制器的构造函数正在执行时,不会注入任何 FXML 字段——这意味着 loginWindow
是 null
。原因不言而喻:FXMLLoader
必须先构造控制器实例,然后才能开始注入适当的字段。
事件的顺序是:(1) 控制器实例化,(2) 字段注入,(3) 初始化方法被调用;我相信链接任何事件 handlers/change 侦听器都包含在第二步中。这意味着任何需要发生的关于 FXML 字段的初始化都应该在 initialize
方法中完成。
您的 HomeScreen
构造函数中存在与 super(homeWindow)
相同的问题,但还有其他问题将在下一点中解决。
除了尝试访问构造函数中尚未注入的字段外,还有以下两个问题:
@FXML
private static AnchorPane homeWindow = new AnchorPane();
第一个问题是您初始化了一个要注入的字段。 永远不要这样做。一个好的经验法则是:如果该字段用 @FXML
注释,则不要手动为其分配值。 FXML 字段最终将被注入,这意味着您事先分配给它的任何值都将被简单地替换。这可能会导致一些细微的问题,因为任何引用先前值的代码都不会使用实际添加到场景图中的对象。
另一个问题是您的字段是静态的。 JavaFX 8+ 不支持注入静态字段。据我所知,它在旧版本中曾经是可能的,但这种行为从未得到官方支持(即是一个实现细节)。此外,让一些固有的基于实例的东西(FXML+控制器)设置一个会影响所有实例的静态字段是没有意义的。
一个额外的问题:当你使 homeWindow
成为非静态时,你不能再使用 super(homeWindow)
因为你不能在调用超级构造函数之前引用它。
使用两个修改后的 类 应该允许您的代码 运行:
LoginScreen.java:
public class LoginScreen extends ScreenController {
@FXML private AnchorPane loginWindow;
@FXML private Button goButton;
@Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
setWindow(loginWindow); // set window in initialize method
}
@FXML
public void goButtonPressed(ActionEvent event) throws IOException {
setScreen("Home");
System.out.println("Success.");
}
}
HomeScreen.java:
public class HomeScreen extends ScreenController {
@FXML private AnchorPane homeWindow;
@Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
setWindow(homeWindow); // set window in initialize method
}
}
但是不要使用:
window.getChildren().setAll(root);
在您的 ScreenController#setScreen
方法中,它会导致一个微妙的问题。您正在添加 root
作为 window
节点的子节点。但是当这种情况发生时,ScreenController
的新实例(与新的 root
关联)有它的 window == root
。换句话说,用 LoginScreen
创建的 window
现在是用 HomeScreen
创建的 window
的父级。根据更复杂的应用程序的设计方式,这可能导致 "roots" 的嵌套越来越深。
也就是说,您已经有了另一种方法,您实际上可以替换整个 Scene
。正如您所说,您在那里遇到的问题是 Stage
调整大小以适应新的 Scene
。这可以通过替换 Scene
的 root
而不是 Scene
本身来解决:
window.getScene().setRoot(root);
一些可能有用的资源:
- Introduction to FXML
- JavaFX FXML Tutorial (javacodegeeks.com)
- JavaFX FXML (jenkov.com)
- How to swap screens in a javafx-application in the controller-class?
- Loading new fxml in the same scene
- Passing Parameters JavaFX FXML
- Switch between panes in JavaFX
- afterburner.fx
- mvvmFX
- Curated list of JavaFX-related things
您可以在初始化期间获取 JavaFX 应用程序的主要阶段。其他场景 类 应该有一个带有 getter 和 setter 的 Stage 字段,这样您就可以通过他们的 Controller 传递主舞台。至于 window 调整大小,您可以通过在 setScene()
语句中添加 getStage().getWidth()
和 getStage().getHeight()
来解决这个问题。
我要指出的一个小例子:
public class MainClass extends Application {
@Override
public void start(Stage stage) throws Exception {
InputStream sceneStream = MainClass.class.getResourceAsStream("/fxml"+
"/newScene/main.fxml");
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(sceneStream);
Scene scene = new Scene(root);
stage.setTitle("App title");
NewScene controller = loader.getController();
controller.setMainStage(stage);
stage.setScene(scene, stage.getWidth(), stage.getHeight());
stage.show();
}
所以上面的函数是从创建主舞台的MainClass开始的。请注意中间部分与代码的其余部分有点分离,通过获取已加载 Scene
的控制器,我将阶段传递给它。您可以通过这种方式将舞台传递到所有场景。还要注意设置场景的部分,我在这里使用了两个从舞台中提取的参数;宽度和高度。除此之外,还有更多方法可以在主舞台上运行的几乎每个场景中获得舞台,只需执行以下操作:
@FXML private Button aButton;
public Button getAButton(){
return aButton;
}
Stage stage = (Stage) getAButton().getScene().getWindow();
这将适用于所有基于初级阶段的场景,无论类型如何,只需要您在场景图表中注册Node
。
我目前有 3 个 classes。
ScreenController(控制器class):
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;
public class ScreenController implements Initializable
{
private AnchorPane window;
public ScreenController()
{
super();
}
public ScreenController(AnchorPane window)
{
setWindow(window);
}
public void setWindow(AnchorPane window)
{
this.window = window;
}
public void setScreen(String screen)
{
try
{
Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
window.getChildren().setAll(root);
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Override
public void initialize(URL location, ResourceBundle resources)
{
}
}
LoginScreen(主屏幕):
import com.app.client.java.controllers.ScreenController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import java.io.IOException;
public class LoginScreen extends ScreenController
{
@FXML
private AnchorPane loginWindow;
@FXML
private Button goButton;
public LoginScreen()
{
super();
setWindow(loginWindow);
}
@FXML
public void goButtonPressed(ActionEvent event) throws IOException
{
setScreen("Home");
System.out.println("Success.");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="loginWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" opacity="0.5" prefHeight="500.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.LoginScreen">
<children>
<Button fx:id="goButton" layoutX="205.0" layoutY="60.0" mnemonicParsing="false" onAction="#goButtonPressed" text="Button" />
</children>
</AnchorPane>
主屏幕(副屏):
import com.app.client.java.controllers.ScreenController;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;
public class HomeScreen extends ScreenController
{
@FXML
private static AnchorPane homeWindow = new AnchorPane();
public HomeScreen()
{
super (homeWindow);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="homeWindow" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.HomeScreen">
<children>
<TextArea layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0" text="aksajkasjkasja" />
</children>
</AnchorPane>
我希望能够使用 setScreen() 函数从主屏幕移动到辅助屏幕。但是,我发现该过程没有成功完成。
我发现另一种可行的方法是(虽然它调整了 window 的大小,而不是用新的内容填充初始 window):
Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
Stage stage = (Stage) loginWindow.getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
但是,我更愿意使用初始实现,因为它更简洁、可读性更强,而且从理论上讲,它提供了我想要的确切行为。
您目前拥有的有几个问题:
在您的
LoginScreen
构造函数中,您使用尚未注入的字段的值调用setWindow
:public LoginScreen() { super(); setWindow(loginWindow); }
当控制器的构造函数正在执行时,不会注入任何 FXML 字段——这意味着
loginWindow
是null
。原因不言而喻:FXMLLoader
必须先构造控制器实例,然后才能开始注入适当的字段。事件的顺序是:(1) 控制器实例化,(2) 字段注入,(3) 初始化方法被调用;我相信链接任何事件 handlers/change 侦听器都包含在第二步中。这意味着任何需要发生的关于 FXML 字段的初始化都应该在
initialize
方法中完成。您的
HomeScreen
构造函数中存在与super(homeWindow)
相同的问题,但还有其他问题将在下一点中解决。除了尝试访问构造函数中尚未注入的字段外,还有以下两个问题:
@FXML private static AnchorPane homeWindow = new AnchorPane();
第一个问题是您初始化了一个要注入的字段。 永远不要这样做。一个好的经验法则是:如果该字段用
@FXML
注释,则不要手动为其分配值。 FXML 字段最终将被注入,这意味着您事先分配给它的任何值都将被简单地替换。这可能会导致一些细微的问题,因为任何引用先前值的代码都不会使用实际添加到场景图中的对象。另一个问题是您的字段是静态的。 JavaFX 8+ 不支持注入静态字段。据我所知,它在旧版本中曾经是可能的,但这种行为从未得到官方支持(即是一个实现细节)。此外,让一些固有的基于实例的东西(FXML+控制器)设置一个会影响所有实例的静态字段是没有意义的。
一个额外的问题:当你使
homeWindow
成为非静态时,你不能再使用super(homeWindow)
因为你不能在调用超级构造函数之前引用它。
使用两个修改后的 类 应该允许您的代码 运行:
LoginScreen.java:
public class LoginScreen extends ScreenController {
@FXML private AnchorPane loginWindow;
@FXML private Button goButton;
@Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
setWindow(loginWindow); // set window in initialize method
}
@FXML
public void goButtonPressed(ActionEvent event) throws IOException {
setScreen("Home");
System.out.println("Success.");
}
}
HomeScreen.java:
public class HomeScreen extends ScreenController {
@FXML private AnchorPane homeWindow;
@Override
public void initialize(URL location, ResourceBundle resources) {
super.initialize(location, resources);
setWindow(homeWindow); // set window in initialize method
}
}
但是不要使用:
window.getChildren().setAll(root);
在您的 ScreenController#setScreen
方法中,它会导致一个微妙的问题。您正在添加 root
作为 window
节点的子节点。但是当这种情况发生时,ScreenController
的新实例(与新的 root
关联)有它的 window == root
。换句话说,用 LoginScreen
创建的 window
现在是用 HomeScreen
创建的 window
的父级。根据更复杂的应用程序的设计方式,这可能导致 "roots" 的嵌套越来越深。
也就是说,您已经有了另一种方法,您实际上可以替换整个 Scene
。正如您所说,您在那里遇到的问题是 Stage
调整大小以适应新的 Scene
。这可以通过替换 Scene
的 root
而不是 Scene
本身来解决:
window.getScene().setRoot(root);
一些可能有用的资源:
- Introduction to FXML
- JavaFX FXML Tutorial (javacodegeeks.com)
- JavaFX FXML (jenkov.com)
- How to swap screens in a javafx-application in the controller-class?
- Loading new fxml in the same scene
- Passing Parameters JavaFX FXML
- Switch between panes in JavaFX
- afterburner.fx
- mvvmFX
- Curated list of JavaFX-related things
您可以在初始化期间获取 JavaFX 应用程序的主要阶段。其他场景 类 应该有一个带有 getter 和 setter 的 Stage 字段,这样您就可以通过他们的 Controller 传递主舞台。至于 window 调整大小,您可以通过在 setScene()
语句中添加 getStage().getWidth()
和 getStage().getHeight()
来解决这个问题。
我要指出的一个小例子:
public class MainClass extends Application {
@Override
public void start(Stage stage) throws Exception {
InputStream sceneStream = MainClass.class.getResourceAsStream("/fxml"+
"/newScene/main.fxml");
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(sceneStream);
Scene scene = new Scene(root);
stage.setTitle("App title");
NewScene controller = loader.getController();
controller.setMainStage(stage);
stage.setScene(scene, stage.getWidth(), stage.getHeight());
stage.show();
}
所以上面的函数是从创建主舞台的MainClass开始的。请注意中间部分与代码的其余部分有点分离,通过获取已加载 Scene
的控制器,我将阶段传递给它。您可以通过这种方式将舞台传递到所有场景。还要注意设置场景的部分,我在这里使用了两个从舞台中提取的参数;宽度和高度。除此之外,还有更多方法可以在主舞台上运行的几乎每个场景中获得舞台,只需执行以下操作:
@FXML private Button aButton;
public Button getAButton(){
return aButton;
}
Stage stage = (Stage) getAButton().getScene().getWindow();
这将适用于所有基于初级阶段的场景,无论类型如何,只需要您在场景图表中注册Node
。