JavaFX getUserData()导致NullPointerException

JavaFX getUserData()导致NullPointerException,javafx,Javafx,我目前正在研究在JavaFX项目中保存SQL连接的最简单方法,以便在所有控制器中使用它。 由于我在边栏FXML文件中创建控制器,因此无法将对象从一个控制器传递到另一个控制器 因此,我想使用Node.setUserData()方法,将连接对象保存到根节点。不幸的是,当我想调用它时,我会得到空指针 保存它可以很好地工作: myStage.getScene().getRoot().setUserData(con); 从同一个stage变量调用它也可以: ... = (Connection) mySt

我目前正在研究在JavaFX项目中保存SQL连接的最简单方法,以便在所有控制器中使用它。 由于我在边栏FXML文件中创建控制器,因此无法将对象从一个控制器传递到另一个控制器

因此,我想使用Node.setUserData()方法,将连接对象保存到根节点。不幸的是,当我想调用它时,我会得到空指针

保存它可以很好地工作:

myStage.getScene().getRoot().setUserData(con);
从同一个stage变量调用它也可以:

... = (Connection) myStage.getScene().getRoot().getUserData();
但是我正在通过访问我的Sidebar.fxml中的stage

Stage stage = (Stage) myButton.getScene().getWindow();
当通过访问UserData时,什么会导致空指针

stage.getScene().getRoot().getUserData();
我知道这是因为它不是“完全相同”的阶段变量。但是它必须是同一个阶段(当我在那里显示一个新视图时,它显示在与以前相同的阶段上)

如何找到与我以前保存UserData的节点完全相同的节点?或者有没有一种方法可以从我没有舞台的另一个上下文访问同一个节点

编辑:我在这里放了一个MCVE来说明我的问题是什么: 编辑:代码现在正在Github中工作,我将在这里发布带有初始问题的代码:

MyMcveStarter.java

    package myMcve;

import myMcve.controller.LoginController;
import javafx.application.Application;
import javafx.stage.Stage;

public class MyMcveStarter extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        LoginController controller = new LoginController(primaryStage);
        controller.displaySceneOn(primaryStage);
    }
}
LoginController.java

    package myMcve.controller;

import myMcve.view.LoginSceneView;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;


public class LoginController {

    private LoginSceneView view;
    private Parent scene;
    Stage myStage;

    String defaultUrl;
    String defaultName;
    String defaultPassword;



    public LoginController(Stage stage) {

            defaultUrl = "jdbc:mysql://localhost:3306/db";
            defaultName = "root";
            defaultPassword = "localhost";

        myStage = stage;

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("../view/LoginScene.fxml"));
        try {
            scene = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        view = loader.getController();
    }

    public void displaySceneOn(Stage stage) {
        stage.setTitle("login");
        Scene myScene = new Scene(scene, 1250, 650);

        stage.setScene(myScene);
        stage.show();

        try {
            initializeDbConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void initializeDbConnection() throws SQLException {

            try {
                DriverManager.setLoginTimeout(15);
                Connection con = DriverManager.getConnection(defaultUrl, defaultName, defaultPassword);

                UserManagementController controller = new UserManagementController(myStage, con);
                controller.displaySceneOn(myStage);

            } catch (Exception e) {
            }
        }


}
package myMcve.controller;

import myMcve.controller.LevelManagementController;
import myMcve.controller.LoginController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class SideBarController{
    @FXML
    private Button levelManBtn;

    public Button getLevelManBtn() {
        return levelManBtn;
    }


    @FXML
    private void levelMan(ActionEvent event){
        //start other Controller from here (SideBar)
        //how do I access the DB Connection here?
        Stage stage = (Stage) levelManBtn.getScene().getWindow();
        //LevelManagementController controller = new LevelManagementController(stage, con);
        //controller.displaySceneOn(stage);
    }


}
package myMcve.controller;

import com.sun.prism.impl.Disposer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javafx.util.Callback;
import myMcve.view.UserManagementView;

import java.io.IOException;
import java.sql.*;

public class UserManagementController{

    private UserManagementView view;
    private Parent scene;
    Stage myStage;
    Connection con;

    public UserManagementController(Stage stage, Connection con){

        myStage = stage;
        this.con = con;

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));

        try {
            scene = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        view = loader.getController();

    }

    public void displaySceneOn(Stage stage){
        stage.setTitle("user management");
        Scene myScene = new Scene(scene, 1250, 650);

        stage.setScene(myScene);

        stage.show();
    }

}
SideBarController.java

    package myMcve.controller;

import myMcve.view.LoginSceneView;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;


public class LoginController {

    private LoginSceneView view;
    private Parent scene;
    Stage myStage;

    String defaultUrl;
    String defaultName;
    String defaultPassword;



    public LoginController(Stage stage) {

            defaultUrl = "jdbc:mysql://localhost:3306/db";
            defaultName = "root";
            defaultPassword = "localhost";

        myStage = stage;

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("../view/LoginScene.fxml"));
        try {
            scene = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        view = loader.getController();
    }

    public void displaySceneOn(Stage stage) {
        stage.setTitle("login");
        Scene myScene = new Scene(scene, 1250, 650);

        stage.setScene(myScene);
        stage.show();

        try {
            initializeDbConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void initializeDbConnection() throws SQLException {

            try {
                DriverManager.setLoginTimeout(15);
                Connection con = DriverManager.getConnection(defaultUrl, defaultName, defaultPassword);

                UserManagementController controller = new UserManagementController(myStage, con);
                controller.displaySceneOn(myStage);

            } catch (Exception e) {
            }
        }


}
package myMcve.controller;

import myMcve.controller.LevelManagementController;
import myMcve.controller.LoginController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class SideBarController{
    @FXML
    private Button levelManBtn;

    public Button getLevelManBtn() {
        return levelManBtn;
    }


    @FXML
    private void levelMan(ActionEvent event){
        //start other Controller from here (SideBar)
        //how do I access the DB Connection here?
        Stage stage = (Stage) levelManBtn.getScene().getWindow();
        //LevelManagementController controller = new LevelManagementController(stage, con);
        //controller.displaySceneOn(stage);
    }


}
package myMcve.controller;

import com.sun.prism.impl.Disposer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javafx.util.Callback;
import myMcve.view.UserManagementView;

import java.io.IOException;
import java.sql.*;

public class UserManagementController{

    private UserManagementView view;
    private Parent scene;
    Stage myStage;
    Connection con;

    public UserManagementController(Stage stage, Connection con){

        myStage = stage;
        this.con = con;

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));

        try {
            scene = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        view = loader.getController();

    }

    public void displaySceneOn(Stage stage){
        stage.setTitle("user management");
        Scene myScene = new Scene(scene, 1250, 650);

        stage.setScene(myScene);

        stage.show();
    }

}
UserManagementController.java

    package myMcve.controller;

import myMcve.view.LoginSceneView;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;


public class LoginController {

    private LoginSceneView view;
    private Parent scene;
    Stage myStage;

    String defaultUrl;
    String defaultName;
    String defaultPassword;



    public LoginController(Stage stage) {

            defaultUrl = "jdbc:mysql://localhost:3306/db";
            defaultName = "root";
            defaultPassword = "localhost";

        myStage = stage;

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("../view/LoginScene.fxml"));
        try {
            scene = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        view = loader.getController();
    }

    public void displaySceneOn(Stage stage) {
        stage.setTitle("login");
        Scene myScene = new Scene(scene, 1250, 650);

        stage.setScene(myScene);
        stage.show();

        try {
            initializeDbConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void initializeDbConnection() throws SQLException {

            try {
                DriverManager.setLoginTimeout(15);
                Connection con = DriverManager.getConnection(defaultUrl, defaultName, defaultPassword);

                UserManagementController controller = new UserManagementController(myStage, con);
                controller.displaySceneOn(myStage);

            } catch (Exception e) {
            }
        }


}
package myMcve.controller;

import myMcve.controller.LevelManagementController;
import myMcve.controller.LoginController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class SideBarController{
    @FXML
    private Button levelManBtn;

    public Button getLevelManBtn() {
        return levelManBtn;
    }


    @FXML
    private void levelMan(ActionEvent event){
        //start other Controller from here (SideBar)
        //how do I access the DB Connection here?
        Stage stage = (Stage) levelManBtn.getScene().getWindow();
        //LevelManagementController controller = new LevelManagementController(stage, con);
        //controller.displaySceneOn(stage);
    }


}
package myMcve.controller;

import com.sun.prism.impl.Disposer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javafx.util.Callback;
import myMcve.view.UserManagementView;

import java.io.IOException;
import java.sql.*;

public class UserManagementController{

    private UserManagementView view;
    private Parent scene;
    Stage myStage;
    Connection con;

    public UserManagementController(Stage stage, Connection con){

        myStage = stage;
        this.con = con;

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));

        try {
            scene = loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        view = loader.getController();

    }

    public void displaySceneOn(Stage stage){
        stage.setTitle("user management");
        Scene myScene = new Scene(scene, 1250, 650);

        stage.setScene(myScene);

        stage.show();
    }

}
LoginSceneView.java

package myMcve.view;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class LoginSceneView {
    @FXML
    private Label label;

    public Label getLabel() {
        return label;
    }

}
LoginScene.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.112-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.LoginSceneView">

    <Label fx:id="label" text="login Buttons etc..." />

</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.UserManagementView">
    <left>
        <!-- SideBar import -->
        <fx:include fx:id="sidebar" source="SideBar.fxml" />
    </left>
    <center>
        <Label fx:id="label" text="user managemnt tableview and Buttons etc..." />
    </center>
</BorderPane>
UserManagementScene.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.112-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.LoginSceneView">

    <Label fx:id="label" text="login Buttons etc..." />

</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.UserManagementView">
    <left>
        <!-- SideBar import -->
        <fx:include fx:id="sidebar" source="SideBar.fxml" />
    </left>
    <center>
        <Label fx:id="label" text="user managemnt tableview and Buttons etc..." />
    </center>
</BorderPane>

我尝试使用与您使用的设计类似的设计,在该设计中,您通过使用FXML/JavaFX控制器对作为MVC视图来实现传统的MVC体系结构,并创建一个单独的MVC控制器。最后,我认为它太复杂而不予理睬

FXML控制器设计中隐含的体系结构实际上是MVC的一个变体,称为MVP(“模型视图演示器”),如果您阅读关于UI体系结构的文章,它与他称之为“被动视图”的变体非常接近。在这种体系结构中,FXML文件表示视图,它本质上是完全被动的,只定义布局。JavaFX控制器代表“Presenter”,它观察和更新模型,并通过修改视图来响应模型中的更改。我的主要建议是重构您的设计,使之与此一致:因此,完全移除“控制器”层中的一个,并考虑“JavaFX控制器”在MVC变体中实现控制器/演示者的一般角色。(我发布了一个更完整的答案。)

特别是,当您使用
时,您的设计会遇到困难。问题在于您的设计是“以控制器为中心的”,即您喜欢创建控制器并让控制器创建视图。使用
基本上为您创建一个新视图(通过加载另一个FXML文件),然后为您创建控制器。所以这更“以视图为中心”。这两者之间的冲突使得在控制器之间共享模型(或服务)变得很棘手

实现此功能的一种方法是在
fxmloader
上设置控制器工厂。控制器工厂是一个将类(由FXML文件中的
fx:controller
属性定义)映射到JavaFX控制器实例的函数。因此,您可以使用此工厂创建控制器,其中的连接已在控制器中初始化。(顺便说一句,连接在您的应用程序中扮演着MVC模型的角色:您可能希望在以后的阶段将其重构为更健壮的模型。您至少应该将所有特定于数据库的代码考虑到一个数据访问对象中,并共享该对象,而不是原始连接。)

首先,在
SideBarController
中定义一个接受连接的构造函数:

public class SideBarController{
    @FXML
    private Button levelManBtn;

    private final Connection con ;

    public SideBarController(Connection con) {
        this.con = con ;
    }

    // existing code (which obviously can now access the connection)...

}
现在,当您加载
UserManagementView
时,请指定一个控制器工厂,该工厂调用采用
连接的构造函数(如果存在),否则调用无参数构造函数。这里展示的方法使用反射,基本上类似于依赖注入(这实际上是您在这里尝试的)框架的实现方式。还有其他的可能性

public UserManagementController(Stage stage, Connection con){

    myStage = stage;
    this.con = con;

    FXMLLoader loader = new FXMLLoader();

    // note that this resource name will likely not work if you bundle the app as a jar....        
    loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));

    loader.setControllerFactory((Class<?> controllerType) -> {

        try {
            // check constructors of controllerType to see if one takes a Connection:
            for (Constructor<?> c : controllerType.getConstructors()) {
                if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Connection.class)) {
                    // found matching constructor, invoke it with the connection as parameter:
                    return c.newInstance(con);
                }
            }

            // no matching constructor, just invoke default constructor:
            return controllerType.newInstance();
        } catch (Exception e) {
            // fatal...
            throw new RuntimeException(e);
        }
    });

    try {
        scene = loader.load();
    } catch (IOException e) {
        e.printStackTrace();
    }
    view = loader.getController();

}
公共用户管理控制器(阶段、连接控制){
myStage=舞台;
this.con=con;
FXMLLoader=新的FXMLLoader();
//请注意,如果您将应用程序捆绑为一个jar,则此资源名称可能不起作用。。。。
setLocation(getClass().getResource(“../view/UserManagementScene.fxml”);
loader.setControllerFactory((类控制器类型)->{
试一试{
//检查controllerType的构造函数,以查看是否有连接:
对于(构造函数c:controllerType.getConstructors()){
如果(c.getParameterCount()==1&&c.getParameterTypes()[0].equals(Connection.class)){
//找到匹配的构造函数,以连接作为参数调用它:
返回c.newInstance(con);
}
}
//没有匹配的构造函数,只调用默认构造函数:
返回controllerType.newInstance();
}捕获(例外e){
//致命的。。。
抛出新的运行时异常(e);
}
});
试一试{
scene=loader.load();
}捕获(IOE异常){
e、 printStackTrace();
}
view=loader.getController();
}
控制器工厂被传播到
d FXML文件的加载进程。因此,当加载
UserManagementScene.fxml
时,指定的控制器是
UseManagementView
。没有构造函数进行
连接
,因此将调用默认构造函数。当遇到
侧边栏.fxml的
时,它指定了
侧边栏控制器的一个控制器,该控制器(现在)有一个构造函数进行
连接
,以便调用构造函数

注tha