jake2389
jake2389

Reputation: 1174

How to pass an ArrayList from one Scene to another in JavaFX 2?

So I've been trying for hours to solve this and it has been answered before in these forums but I would really appreciate some help since none really address my issue specifically or propose very differing solutions and I'm not quite sure what to try (even though I've tried all of them, I daresay).

The issue is this: I have a JavaFX application with 6 Scenes and their respective Controllers. I have a Person class that creates an object and that object I would like to store in an ArrayList that's global (that is, accessible by all the Scenes) so that when I close the application I can call another method to save it to a serialized file.

I don't actually use the ArrayList in the first couple of scenes because they're menus, so the actual method of creating the object is not run until the third Scene or so.

I have successfully created the Scenes and I can switch between them, I just have no idea

  1. Where to declare the ArrayList (if in the main method, the main Class or where?)
  2. How to pass this ArrayList between the Scenes so that they will all be able to access them.

I currently use this Class to switch between the Scenes:

public class SelectScene  {
    public void setScene(String fxmlFileName, String title, ActionEvent event) throws IOException{
        Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
        Parent root = FXMLLoader.load(getClass().getResource(fxmlFileName));
        Scene scene = new Scene(root);
        scene.getStylesheets().add(FinalDossier.class.getResource("style.css").toExternalForm());
        stage.setTitle(title);
        stage.setScene(scene);
    }

I appreciate any help :)

Upvotes: 2

Views: 3291

Answers (2)

James_D
James_D

Reputation: 209408

There are several ways to do this: basically all of them involve not using the static FXMLLoader.load(URL) method, but creating an FXMLLoader instance instead.

One way would be to create the controllers in your code, initialize them with the shared List, and then pass them to setController(...). In this version you would remove the fx:controller attributes from your FXML files.

It seems like a better way to do this in your case, though, would be to define a controller factory for the FXMLLoader. This is an object that tells the FXMLLoader how to get an instance from the class name defined in the fx:controller attribute. This just involves a little bit of reflection magic. So you would do something like

public class SelectScene  {


    private Callback<Class<?>, Object> controllerFactory ;

    public SelectScene(final List<...> data) {
        controllerFactory = new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> type) {
                try {
                    Constructor<?> constructor = type.getDeclaredConstructor(List.class);
                    return constructor.newInstance(data);
                } catch (NoSuchMethodException exc) {
                    return type.newInstance();
                } catch (Exception ex) {
                    // trouble...
                    ex.printStackTrace();
                    return null ;
                }
            }
         };
    }

    public void setScene(String fxmlFileName, String title, ActionEvent event) throws IOException{
        Stage stage = (Stage) ((Node)event.getSource()).getScene().getWindow();
        FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlFileName));
        loader.setControllerFactory(controllerFactory);
        Parent root = (Parent)loader.load();
        Scene scene = new Scene(root);
        scene.getStylesheets().add(FinalDossier.class.getResource("style.css").toExternalForm());
        stage.setTitle(title);
        stage.setScene(scene);
    }
}

For the controllers that need access to the shared data, just define a constructor with a parameter taking a List:

public class MyController {
    private final List<...> data ;
    public MyController(List<...> data) {
        this.data = data ;
    }
    public void initialize() {
        // usual initialize method
    }
}

For controllers without that constructor, the controller factory will fall back to the default constructor.

You can define the List wherever it's convenient: in the SelectScene class directly (as shown in the code), or you could define it earlier (in your start() method, for example), and pass it into the SelectScene constructor. It just depends where else you might need access to it.

UPDATE: Full example here

Upvotes: 3

zella
zella

Reputation: 4685

I recommended to see architecture of my javafx-MultipleScreens-framework. In my sample you can store arraylist in MyScreenController class, because all screens have reference to it

Upvotes: 1

Related Questions