使用滚动窗格 javafX 时出现空指针异常

Null Pointer Exception when using Scroll pane javafX

我一直在构建电影院预订应用程序,并试图创建一个显示电影和放映时间的场景。当我使用锚定窗格和 vbox 来显示所有信息时,它可以工作,但是当我尝试插入一个额外的滚动窗格(在 scenebuilder 中)时,FXML 加载器 returns 一个空指针异常,我无法弄清楚为什么.. .

这是我的 FXML 代码

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

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="598.0" prefWidth="798.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MovieShowingsController">
   <children>
      <MenuBar>
        <menus>
          <Menu mnemonicParsing="false" text="myBookings">
            <items>
              <MenuItem mnemonicParsing="false" text="Close" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
      <ScrollPane fx:id="scrollpane" hbarPolicy="NEVER" layoutY="22.0" prefHeight="576.0" prefWidth="798.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="22.0">
         <content>
            <VBox fx:id="vbox" prefHeight="555.0" prefWidth="740.0" />
         </content>
      </ScrollPane>
   </children>
</AnchorPane>

这里是控制器class

public class MovieShowingsController {

    @FXML
    private VBox vbox;

    private ArrayList<FilmInfo> filmList;

    private ArrayList<Screening> screeningList;

    private MovieShowings showings;

    //FXML loader instance variable to be accessed by dynamic scene controller
    private VBox holder;

    @FXML
    private void initialize() {
    }

    public MovieShowingsController() {

    }

    public MovieShowingsController(MovieShowings showings) {

        String date = "2019-04-15";
        Date sqlDate = Date.valueOf(date);

         System.out.println("\n");
         System.out.println("***Screenings for " + date + "***");

         filmList = new ArrayList();
         screeningList = DatabaseConnection.getInstance().retrieveScreeningsForDay(sqlDate);

         for (Screening screeningInstance : screeningList) {

             if (!filmList.contains(screeningInstance.getFilmInfo())) {

                 filmList.add(screeningInstance.getFilmInfo());

             }

             System.out.println(screeningInstance.toString());
          }

        Collections.sort(screeningList);

        this.showings = showings;

        //populating FXML instance variable from loader
        this.holder = (VBox) showings.getRoot().lookup("#vbox");

        buildMovieShowings(holder);
    }

    private void buildMovieShowings(VBox holder) {

        holder.setSpacing(50);

        for (int i = 0; i < filmList.size(); i++) {

            VBox infoHolder = new VBox();

            infoHolder.setSpacing(10);

            Label title = new Label(String.format("%s%8s", filmList.get(i).getTitle(),
                    "(" + filmList.get(i).getRating() + ")"));

            title.setStyle("-fx-font: 24 arial;");

            Label duration = new Label(String.format("%s%s%s", "Film Length: ",
                    filmList.get(i).getDuration(), " mins"));

            duration.setStyle("-fx-font: 24 arial;");

            Label director = new Label(String.format("%s%s", "Directed By: ",
                    filmList.get(i).getDirector()));

            director.setStyle("-fx-font: 24 arial;");

            infoHolder.getChildren().addAll(title, duration, director);

            HBox timesHolder = new HBox();

            timesHolder.setSpacing(10);

            for (int j = 0; j < screeningList.size(); j++) {

                if (screeningList.get(j).getFilmInfo().equals(filmList.get(i))){

                    Label time = new Label();

                    Background black = new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY));

                    Background red = new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY));

                    time.setBackground(black);

                    Screen screen = screeningList.get(j).getScreen();
                    Screening screening = screeningList.get(j);

                    time.setOnMouseClicked(new EventHandler<MouseEvent>() {

                    @Override
                    public void handle(MouseEvent e) {

                        try {

                        System.out.println(screening.getFilmInfo().getTitle() + screening.getShowTime());

                        time.setBackground(red);

                        SeatMap seatMap = new SeatMap();

                        SeatMapController seatMapController = new SeatMapController(seatMap, 
                                screen);

                        Scene seatMapScene = seatMap.getScene();

                        Stage window = (Stage) ((Node) e.getSource()).getScene().getWindow();

                        window.setResizable(false);

                        window.setWidth(800);
                        window.setHeight(600);

                        window.setScene(seatMapScene);

                        window.show();

                        }

                        catch (IOException ex) {

                            ex.printStackTrace();

                            }

                        }

                    });

                    time.setPrefSize(100, 100);

                    time.setAlignment(Pos.CENTER);

                    time.setStyle("-fx-border-color: black");

                    time.setStyle("-fx-font: 22 arial;");

                    time.setStyle("-fx-text-fill: white");

                    time.setText(screeningList.get(j).getShowTime());

                    timesHolder.getChildren().add(time);
                }
            }

            infoHolder.getChildren().add(timesHolder);

            holder.getChildren().add(infoHolder);
        }
    }
}

主要class

public class MovieShowings{

    private AnchorPane root;

    public MovieShowings() {

        try {

            root = FXMLLoader.load(getClass().getResource("movieshowings.fxml"));
        }

        catch(IOException e){

            e.printStackTrace();
        }

    }


    public Scene getScene() {

    Scene scene = new Scene(root,800,600);

    return scene;

    }

    public AnchorPane getRoot() {

        return root;
    }

}

以及用户登录后调用它的代码

if(DatabaseConnection.getInstance().login(Username.getText(), Password.getText())) {

            MovieShowings films = new MovieShowings();

            MovieShowingsController filmsController = new MovieShowingsController(films);

            Scene movieShowings = films.getScene();

            Stage window = (Stage) ((Node) e.getSource()).getScene().getWindow();

            window.setScene(movieShowings);

            window.show();

关于如何解决这个问题有什么想法吗?

编辑:fx:id 'vbox' 未从 getRoot() 方法访问,即使它已被注入 FXML 加载器

这是因为 ScrollPane 添加了内容,ScrollBars 等。在创建皮肤时的第一个布局过程中到场景。此布局传递发生在 JavaFX 应用程序线程 "regains control" 之后(即您已完成事件处理程序、Application.start 方法或让 JavaFX 执行代码的类似方式)。

请注意,您使用控制器 class 的方式很奇怪。我建议使用此问题的答案中描述的方法之一与控制器通信:Passing Parameters JavaFX FXML

例如:

public class MovieShowings{

    private AnchorPane root;

    public MovieShowings() {

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("movieshowings.fxml"));
            root = loader.load();
            MovieShowingsController controller = loader.getController();
            controller.initMovieShowings(this);
        }

        catch(IOException e){

            e.printStackTrace();
        }

    }
    ...

}
public class MovieShowingsController {

    ...

    public void initMovieShowings(MovieShowings showings) {
        String date = "2019-04-15";
        Date sqlDate = Date.valueOf(date);

         System.out.println("\n");
         System.out.println("***Screenings for " + date + "***");

         filmList = new ArrayList();
         screeningList = DatabaseConnection.getInstance().retrieveScreeningsForDay(sqlDate);

         for (Screening screeningInstance : screeningList) {

             if (!filmList.contains(screeningInstance.getFilmInfo())) {

                 filmList.add(screeningInstance.getFilmInfo());

             }

             System.out.println(screeningInstance.toString());
          }

        Collections.sort(screeningList);

        this.showings = showings;

        //populating FXML instance variable from loader

        // use the injected field here
        buildMovieShowings(vbox);
    }

    ...
}

由于您实际上并没有在控制器中使用 MovieShowings 对象,因此可以通过从

进行初始化来简化代码
@FXML
private void initialize()

控制器中的方法,并从控制器代码中删除所有 MovieShowings 相关部分。这样你就不必将它传递给控制器​​了。

使用 ListView 使用自定义单元格也可以作为显示电影的选项...