29

Goodevening everyone,

I have found a bunch of posts already on this topic but I still can not manage to pass an object from Controller1 to Controller2. Is there somewhere a full tutorial or some example project that does this?

I've gotten this far until I got stuck:

Country class

public class Country {
private SimpleStringProperty country = new SimpleStringProperty("");

//Constructor
public Country() {
}

//GETTERS
public String getCountry() {
    return country.get();
}

//SETTERS
public void setCountry(String value) {
    country.set(value);
}

@Override
public String toString() {
    return getCountry();
}
}

When the program starts, the main FXML gets loaded (Sample.fxml). This contains a border pane with a menu bar in the top panel and a content pane in the center. On initialize I create a new Country object and store it in a global variable. I have a method that loads another FXML into the content pane when a menu item is clicked:

SampleController.java

public class SampleController implements Initializable {

@FXML
private Pane pContent;

private Country c;

@FXML
private void handleButtonAction(ActionEvent event) throws IOException {
    System.out.println(c); //this prints Belgium, which is correct

    URL url = getClass().getResource("Sub1.fxml");

    FXMLLoader fxmlloader = new FXMLLoader();
    fxmlloader.setLocation(url);
    fxmlloader.setBuilderFactory(new JavaFXBuilderFactory());

    pContent.getChildren().clear();
    pContent.getChildren().add((Node) fxmlloader.load(url.openStream()));
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    c = new Country();
    c.setCountry("Belgium");
}

public Country getCountryFromSampleController(){
    return c;
}
}

Now I wish to capture the Country object when the Sub1.fxml gets loaded, which means I need to fetch the country object on initialize():

Sub1Controller.java

public class Sub1Controller implements Initializable {

/**
 * Initializes the controller class.
 */
@Override
public void initialize(URL url, ResourceBundle rb) {
    SampleController sp = new SampleController(); //I don't know how to fetch the original SampleController object
    System.out.println(sp.getCountryFromSampleController()); 
    //this prints null, which is ofcourse logical because I make a new SampleController object.         
}    
}

The question that I have, how can I get the 'original' SampleController object so I can use the getCountryFromRoot() method to fetch the Country object with value Belgium? I've been searching on this issue for hours and hours and have read every post on StackOverflow about this, but it seems I do not find the missing link... any help (preferably with this code) is appreciated!

Sorry for the long post, I tried to be as complete as possible else I'll never understand...

Perneel
  • 3,317
  • 7
  • 45
  • 66

2 Answers2

54

FXML is a simple form of MVC pattern. FXML file is a view, Controller is obvious, what's missed? The model -- a place where you store all data relative to your current view and, thus, which you can use to share Country data between controllers.


1. One of the possible approach to introduce model is "context". Let's consider a case, then you have only one model for the whole project so you can have a global context in a form of Singleton

public class Context {
    private final static Context instance = new Context();

    public static Context getInstance() {
        return instance;
    }

    private Country country = new Country();

    public Country currentCountry() {
        return country;
    }
}

Your SampleController will have next changes:

@Override
public void initialize(URL url, ResourceBundle rb) {
    Context.getInstance().currentCountry().setCountry("Belgium");
}

And SubController1 can access it the same way:

@Override
public void initialize(URL url, ResourceBundle rb) {
    System.out.println(Context.getInstance().currentCountry().getCountry());
}

2. Another way is to pass context to SubController1 then you load it's xml. It will work better if you don't want to have application global model. So create similar Context class but without instance fields, and:

public class Sub1Controller implements Initializable {
    private Context context;
    public void setContext(Context context) {
        this.context = context;
        // initialize country dependent data here rather then in initialize()
    }
}

Setting context in SampleController:

Context currentContext = new Context();

@Override
public void initialize(URL url, ResourceBundle rb) {
    currentContext.currentCountry().setCountry("Belgium");
}

@FXML
private void handleButtonAction(ActionEvent event) throws IOException {
    URL url = getClass().getResource("Sub1.fxml");

    FXMLLoader fxmlloader = new FXMLLoader();
    fxmlloader.setLocation(url);
    fxmlloader.setBuilderFactory(new JavaFXBuilderFactory());

    pContent.getChildren().clear();
    pContent.getChildren().add((Node) fxmlloader.load(url.openStream()));
            // here we go
    ((Sub1Controller)fxmlloader.getController()).setContext(currentContext);
}
blo0p3r
  • 6,790
  • 8
  • 49
  • 68
Sergey Grinev
  • 34,078
  • 10
  • 128
  • 141
  • 1
    Thanks a million times Sergey, finally it all makes sense. This is the first time I have to create a big application but I'm still struggeling on how to 'build' it. I also have another security question that I'd want to ask, but I'm not sure if this is the right site to ask it (its not code related) – Perneel Aug 29 '12 at 19:12
  • Hey Sergey, sorry to bring up this old topic. This is still working as a charm but what I do wonder... Is there a way to maximize the width and height from the Node object `(Node) fxmlloader.load(url.openStream())` so it is bound to the widht & height of the Pane (pContent)? – Perneel Jan 09 '13 at 20:21
  • it should work automatically, if not, better provide your code in a separate question – Sergey Grinev Jan 10 '13 at 00:10
  • Hi Sergey, as requested: http://stackoverflow.com/questions/14265697/bind-width-and-height-of-dynamically-loaded-fxml – Perneel Jan 10 '13 at 19:25
  • I like the idea of the `Singleton` pattern, but would it be considered bad practice to simple set the variables within the main controller to `static` and make reference to these from sub controllers? – blo0p3r Feb 05 '13 at 14:46
  • 1
    it will work for small project, but generally it's a bad practice, see http://stackoverflow.com/questions/3151768/are-global-static-classes-and-methods-bad/ – Sergey Grinev Feb 05 '13 at 14:58
  • Using option `#2`. If you are using this data in initializing your data, you only get access to your controller AFTER your pane has loaded. Is there any way around this? I'm thinking this could be a question on its own, but since you are touching this already could fit nicely here. – blo0p3r Feb 06 '13 at 14:45
  • This was answered here : http://stackoverflow.com/a/14190310/686036. Simple... should of thought of it. – blo0p3r Feb 06 '13 at 15:23
  • Or even simpler: `public enum Context { INSTANCE; }`. – assylias Aug 11 '13 at 20:42
  • @SergeyGrinev: can you help me with this? http://stackoverflow.com/q/22243243/2722799 – Java Man Mar 10 '14 at 06:56
  • 1
    Thank you @SergeyGrinev This one worked wonders for me. I needed to get table row values on other controllers and your answer aided me in doing this after doing lots and lots of googling. Thank you. – Joseph Jan 13 '16 at 08:48
  • @SergeyGrinev we create only one `Context` for many models ,or each model has specific `Context` ? for example i have Country and Person are my models. – Menai Ala Eddine - Aladdin Mar 26 '18 at 13:30
  • @MenaiAlaEddine it's absolutely up to you. I'd say for smaller projects one Context is more convenient. – Sergey Grinev Mar 26 '18 at 14:50
3

by using the Flow API of DataFX you can inject data in your controller instances by using CDI:

Hendrik Ebbers
  • 2,570
  • 19
  • 34