Brancol
Brancol

Reputation: 1

Connection between controllers and FXML files on different stages

I was learning JavaFX this days and I got stuck on this error here:

I'm creating a simple game, like Tribal Wars / Clash of Clans. My question here is about how I can send information from one stage (window / confirmSerra.fxml / Controller2.java) to another stage (primarystage / aldeiaPage.fxml / aldeiaControl.java) that is already created and running.

I already know how to send information from one stage to another if it's not created yet.

This is my Main.java that starts the principalStage of the game:

public class Main extends Application {


    @Override
    public void start(Stage primaryStage) throws Exception{

        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("aldeiaPage.fxml"));
        Parent root = fxmlLoader.load();

        primaryStage.setTitle("Jogo_Alpha - Aldeia");
        primaryStage.setScene(new Scene(root, 1024, 768));
        primaryStage.show();
    }

    public static void main(String[] args) {

        launch(args);
    }
}

This is some part of the controller "aldeiaControl.java" from the "aldeiaPage.fxml":

@FXML public void teste1() throws Exception {

    Stage window = new Stage();
    FXMLLoader fxmlLoader = new FXMLLoader();
    fxmlLoader.setLocation(getClass().getResource("confirmSerra.fxml"));

    Pane layout = fxmlLoader.load();
    Scene scene = new Scene(layout);

    Controller2 c =  fxmlLoader.getController();
    window.setTitle("Upgrade Serralheria");

    window.setScene(scene);
    window.show();

    c.labelserraLev.setText(String.valueOf(seralevelNum));
    //seralevelNum is a variable from this controller "aldeiaControl"
}

As you can see, I can create a new stage from the button click teste1, that is linked to the @FXML file. And making this FXMLLoader and .getController() give me acess to the labelserraLev that is in the other controller.

Controller2.java

public class Controller2 {


    @FXML public Label labelserraLev;


    @FXML public void buttonclick() throws Exception {

        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.load(getClass().getResource("aldeiaPage.fxml"));

        aldeiaControl labelchange =  fxmlLoader.getController();

        labelchange.seralevel.setText("10");

    }

}

How can I change the label text of the principalstage (that was already created and is still running), when I click this buttonclick() (that is the Upgrade button in the photo below)? I could change the label text of the new window, but because it was a new window, but about a window that is already created, I can't.

The error is this:

Caused by: java.lang.NullPointerException
at sample.Controller2.buttonclick(Controller2.java:30)

enter image description here

Upvotes: 0

Views: 1106

Answers (1)

James_D
James_D

Reputation: 209724

It's generally bad practice to expose the UI components outside of the controller classes: it basically breaks encapsulation. I'd actually go further and suggest that in most cases your controllers should not even need references to each other either.

The FXML is a "View" and with the controller class forms two parts of a (fairly loosely-defined) design pattern called "Model-View-Controller". The part you are missing here is the model. In some variations the controller is replaced by a presenter, and arguably the "controller" attached to an FXML file works more like a presenter.

The basic idea is that the model stores the data: the view presents the data to the user, and the controller processes user input and updates the model in response. In some versions of the pattern, the controller/presenter observe the data in the model and update the view when it changes. (In others, the view observes the model and updates itself when the data change.)

The advantage of this design is that you can have multiple views of the same data, which can stay synchronized without knowing about each other. All view/presenter pairs observe the data in the model, so if one presenter updates the model, all the views get updated via observing it.

So you need a model class that holds the data:

public class Model {

    private final IntegerProperty level = new SimpleIntegerProperty();

    public IntegerProperty levelProperty() {
        return level ;
    }

    public final int getLevel() {
        return levelProperty().get();
    }

    public final void setLevel(int level) {
        levelProperty().set(level);
    }

    // other properties etc...
}

Now your controllers need a reference to the model, and bind their views to it:

public class AldeiaControl {

    private Model model ;

    @FXML
    private Label seraLevel ;

    public void setModel(Model model) {
        this.model = model ;
        seraLevel.textProperty().bind(model.levelProperty().asString());
    }

    @FXML public void teste1() throws Exception {

        Stage window = new Stage();
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("confirmSerra.fxml"));

        Pane layout = fxmlLoader.load();
        Scene scene = new Scene(layout);

        Controller2 c =  fxmlLoader.getController();
        window.setTitle("Upgrade Serralheria");

        window.setScene(scene);
        window.show();

        c.setModel(model);
    }   

}

and

public class Controller2 {


    @FXML private Label labelserraLev;

    private Model model ;

    public void setModel(Model model) {
        this.model = model ;
        labelserraLev.textProperty().bind(model.levelProperty().asString());
    }

    @FXML public void buttonclick() throws Exception {

        // will automatically update all properties bound to the model's level:
        model.setLevel(10);

    }

}

Finally, create a model and give it to the controller:

public class Main extends Application {


    @Override
    public void start(Stage primaryStage) throws Exception{

        Model model = new Model();

        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("aldeiaPage.fxml"));
        Parent root = fxmlLoader.load();

        AldeiaControl controller = fxmlLoader.getController();
        controller.setModel(model);

        primaryStage.setTitle("Jogo_Alpha - Aldeia");
        primaryStage.setScene(new Scene(root, 1024, 768));
        primaryStage.show();
    }

    public static void main(String[] args) {

        launch(args);
    }
}

Also see Applying MVC With JavaFx

Upvotes: 1

Related Questions