0

I've been building a cinema booking application and am trying to create a scene that displays movies and showing times. It works when I have used an anchor pane and vbox to display all the information but when I try to insert an additional scroll pane (in scenebuilder) the FXML loader returns a null pointer exception and I cant work out why...

Here is my FXML code

<?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>

Here is the controller 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);
        }
    }
}

The main 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;
    }

}

and the code that calls it after the user has logged in

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();

Any ideas as how to fix this?

Edit: The fx:id 'vbox' is not being accessed from the getRoot() method even though is has been injected into the FXML loader

Will170393
  • 57
  • 5

1 Answers1

1

The reason for this is that ScrollPane adds content, ScrollBars, ect. to the scene during the first layout pass when it's skin is created. This layout pass happens after the JavaFX application thread "regains control" (i.e. you're done with the event handler, Application.start method or similar way of having JavaFX execute your code).

Note that you're using your controller class in a pretty weird way. I recommend using one of the approaches described in the answers to this question to communicate with the controller: Passing Parameters JavaFX FXML

For example:

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);
    }

    ...
}

Since you don't actually use the MovieShowings object in your controller, the code could be simplified a bit by doing teh initialisation from a

@FXML
private void initialize()

method in the controller and remove every MovieShowings-related part from the controller code. This way you'd get rid of the necessity to pass it to the controller.

Using a ListView using custom cells could also be an option to display the movies...

fabian
  • 80,457
  • 12
  • 86
  • 114