0

Overview

I am working on a simple JavaFX application with multiple scenes using the MVC Architectural pattern. I have the design in place but I am having trouble persisting the User model across the different scenes. At the moment I do not have any backend. I am looking to just instantiate the User model when the application starts and display that single instance across my application. Any changes to that user model will up updated across the scenes. I am not looking to persist after the application closes.

I believe the issue arises from a design choice I had early on in development. I use a Controller named SceneNavigatorControl that calls the class Navigator which handles the presentation of different scenes in the application. These scenes are .fxml files.

SceneNavigatorControl

public class SceneNavigatorControl implements Initializable {

@FXML
private BorderPane mainStage;


@FXML
private void displayHomeScene() {
    Navigator object = new Navigator();
    Pane view = object.getScene("Home");
    handleButtonChange("Home");

    mainStage.setCenter(view);
}

@FXML
private void displayProfileScene() {
    Navigator object = new Navigator();
    Pane view = object.getScene("Profile");
    handleButtonChange("Profile");
    mainStage.setCenter(view);
}


@Override
public void initialize(URL location, ResourceBundle resources) {

    // Called when the application starts, should this be where I instantiate User?
    //User user = new User();
    //user.setName("Bob");

    displayHomeScene();


}

}

The above code handles which scene to take the user to. Depending on which button is tapped. One of the above functions is called and the scene is displayed by using the Navigator Class. I would think this is where I instantiate User and pass it to all other controllers

Navigator Class

public class Navigator {
private Pane view;

public Pane getScene(String fileName) {
    try {
        URL fileUrl = ActivityTracker.class.getResource("/ActivityTracker/Views/" + fileName + ".fxml");
        if (fileUrl == null) {
            throw new java.io.FileNotFoundException("FXML File cannot be found");
        }

        view = new FXMLLoader().load(fileUrl);
    } catch (Exception e) {
        System.out.print("No page " + fileName + " please check FXMLLoader");
    }
    return view;
}

}

The navigator class simple checks if a .fxml file with a given name exists. If it does, it replaces the current scene on the stage with the one given.

HomeView

<AnchorPane fx:id="Home" prefHeight="500.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ActivityTracker.Controllers.HomeController">
<children>
    <Label fx:id="clockLabel" layoutX="90.0" layoutY="32.0" textFill="WHITE" AnchorPane.leftAnchor="90.0" AnchorPane.topAnchor="32.0">
     <font>
        <Font name="Lucida Grande" size="100.0" />
     </font></Label>
  <Pane layoutX="410.0" layoutY="-1.0" prefHeight="500.0" prefWidth="90.0" style="-fx-background-color: #1d1d1d;" />
</children>

This is our view which is created using an .fxml file. On the first line is where we declare what view is associated with what controller.

HomeController

public class HomeController implements Initializable {

@FXML
private Pane Home;

@Override
public void initialize(URL location, ResourceBundle resources) {
    // Print users details 
}

}

ActivityTracker (Main)

public class ActivityTracker extends Application {

public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage primaryStage) {
    try {
        Parent root = FXMLLoader.load(ActivityTracker.class.getResource("Views/Stage.fxml"));
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        primaryStage.show();
    } catch (Exception ex) {
        Logger.getLogger(ActivityTracker.class.getName()).log(Level.SEVERE, null, ex);
    }
}

}

User Model

public class User {

// Define all user stat variables for the model
private String userName;

/**
 * Method that returns the users name
 * @return users name of type String
 */
public String getUsersName() {
    return userName;
}

/**
 * Method that takes in a string value and sets it as the current users name
 * @param  name  the current users weight
 */
public void setUsersName(String name) {
    this.userName = name;
}

}

CrazyCoder
  • 389,263
  • 172
  • 990
  • 904
  • Have a look at https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx/32343342#32343342. Passing around models from controller to controller can get a bit cumbersome; incorporating a dependency injection framework (such as Spring or Guice, etc) may ease the pain (though they have their own learning curves, and there's some work to be done in integrating with JavaFX). – James_D Apr 19 '20 at 18:04
  • Thank you for this. I'll go ahead and give this a read. – Chandler Long Apr 19 '20 at 18:05

1 Answers1

0

The following is an mre of setting one model instance to two controllers.
Define a simple model to be used by the two controllers:

class User {

    private final String fName, lName;

    public User(String fName, String lName) {
        this.fName = fName;
        this.lName = lName;
    }

    public String getFirstName() {
        return fName;
    }

    public String getLastName() {
        return lName;
    }
}

Define two very basic fxml views, each with a controller:

FirstName.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>

<HBox alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 
prefHeight="30.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="fx_tests.FNameController">
   <children>
      <Label text="First Name: " HBox.hgrow="NEVER">
         <font>
            <Font size="16.0" />
         </font>
      </Label>
      <Label fx:id="fName" text="&quot;-&quot;">
         <font>
            <Font size="16.0" />
         </font>
      </Label>
   </children>
</HBox>

LastName.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>

<HBox alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 
prefHeight="30.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="fx_tests.LNameController">
   <children>
      <Label text="Last Name: " HBox.hgrow="NEVER">
         <font>
            <Font size="16.0" />
         </font>
      </Label>
      <Label fx:id="lName" text="&quot;-&quot;">
         <font>
            <Font size="16.0" />
         </font>
      </Label>
   </children>
</HBox>

Define a simple interface:

interface Controller {
    void setModel(User model);
}

And have the two controllers implement it:

import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class FNameController implements Controller{

    @FXML
    private Label fName;

    @Override
    public void setModel(User model) {
        fName.setText(model.getFirstName());
    }
}


import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class LNameController implements Controller{

    @FXML
    private Label lName;

    @Override
    public void setModel(User model) {
        lName.setText(model.getLastName());
    }
}

Define the main view that is the parent of the FirstName.fxml and LastName.fxml:
Main.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<BorderPane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fx_tests.MainController">
   <top>
      <Text strokeType="OUTSIDE" strokeWidth="0.0" text="User Info" textAlignment="CENTER" wrappingWidth="250" BorderPane.alignment="CENTER">
         <font>
            <Font size="20.0" />
         </font>
      </Text>
   </top>
   <center>
      <VBox fx:id="infoPane" alignment="CENTER" spacing="10.0" BorderPane.alignment="CENTER">
         <BorderPane.margin>
            <Insets left="20.0" />
         </BorderPane.margin>
      </VBox>
   </center>
</BorderPane>

The controller of the main view sets the model to the two controllers:

import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;

public class MainController {

    @FXML
    private VBox infoPane;

    @FXML
    void initialize() throws IOException{

        try {
            User model = new User("Ann","Davis"); //initialize model

            FXMLLoader loader = new FXMLLoader(getClass().getResource("FirstName.fxml"));
            Pane fName =  loader.load();
            Controller controller = loader.getController();
            controller.setModel(model); //set model to first name controller

            loader = new FXMLLoader(getClass().getResource("LastName.fxml"));
            Pane lName =  loader.load();
            controller = loader.getController();
            controller.setModel(model); //set model to last name controller

            infoPane.getChildren().addAll(fName, lName);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

Test it all using:

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class FxmlTest extends Application {

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

            Pane root = FXMLLoader.load(getClass().getResource("Main.fxml"));
            primaryStage.setScene(new Scene(root));
            primaryStage.show();
    }

    public static void main(String[] args) {
        launch(null);
    }
}
c0der
  • 18,467
  • 6
  • 33
  • 65