0

I've got an application which i try to keep the mvc rule. I've got a model

public class CarTableView {
SimpleIntegerProperty id = new SimpleIntegerProperty();
SimpleStringProperty brand = new SimpleStringProperty();
SimpleStringProperty engine = new SimpleStringProperty();
SimpleBooleanProperty navi = new SimpleBooleanProperty();
SimpleBooleanProperty available = new SimpleBooleanProperty();
SimpleDoubleProperty liters = new SimpleDoubleProperty();
SimpleIntegerProperty power = new SimpleIntegerProperty();

ObservableList<CarTableView> observableList = FXCollections.observableArrayList();
public CarTableView()
{

}
public CarTableView(int id,String brand,String engine,Boolean navi,Boolean available,double liters,int power)
{
    this.id.set(id);
    this.brand.set(brand);
    this.engine.set(engine);
    this.navi.set(navi);
    this.available.set(available);
    this.liters.set(liters);
    this.power.set(power);
}

Which I need to use in my two controllers in order to get referance to ObservableList. And here is where lie my problem. Where to create the Model? I need him to be created before the initialize() method will invoked

public class MainController {

private CarTableView model = new CarTableView();
@FXML
private Button addVehicleButton;

@FXML
private Button showClientDatabaseButton;

@FXML
public void initialize()
{

(...)
    model.getObservableList().add(new CarTableView(213,"FIAT","1.9 JTD",true,true,32.4,132)); // HERE I use model (it's fine here because its created in this class)
    tableView.setItems(model.getObservableList());

@FXML
public void addNewVehicleButtonClicked() throws IOException
{
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/AddNewCar.fxml"));
    Stage stage = new Stage();
    Scene scene = new Scene((Pane)loader.load());
    stage.setScene(scene);
    stage.show();
    AddNewCarController addNewCarController = loader.getController();
    addNewCarController.initData(model); // HERE i try to initialize model in second controller

}

But I need him also in initialize() method from another controller

public class AddNewCarController {

private ObservableList<String> choiceBoxList = FXCollections.observableArrayList("YES","NO");
@FXML
public void initialize()
{
    autoIncrementChekBox.setSelected(true);
    if(autoIncrementChekBox.isSelected())
    {
        idTextField.setText(Integer.toString((model.getObservableList().size()+1))); // HERE I NEED HIM TOO!! But it is null... he havent been load yet!
        idTextField.setDisable(true);
    }
    comboBox.setItems(choiceBoxList);
    comboBox.setValue("YES");
}

In this situation even if I will pass the data throw function initModel() in second controller. Where I should have create model, and pass to both controller? I will also add my Main() file to clear situation

public class Main extends Application {

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

}

@Override
public void start(Stage stage) throws Exception {
    stage.setTitle("Managment System");
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
    Scene scene = new Scene((Pane)loader.load(),1800,900);
    MainController mainController = loader.getController();
    stage.setScene(scene);
    stage.show();

}

}

Michael213
  • 317
  • 3
  • 11

1 Answers1

1

You can create the controller so that the model is passed to the constructor:

public class MainController {

    private final CarTableView model ;

    public MainController(CarTableView model) {
        this.model = model ;
    }

    // existing code ...

}

To use this version of the controller, do

public class Main extends Application {

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

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("Managment System");
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));

        CarTableView model = new CarTableView();
        MainController mainController = new MainController(model);

        loader.setController(mainController);

        Scene scene = new Scene((Pane)loader.load(),1800,900);
        stage.setScene(scene);
        stage.show();

    }

}

And remove the fx:controller attribute from the FXML file.

You can then do the same when you load the other FXML file, using the same instance of the model.


A related approach is to use a controller factory:

CarTableView model = new CarTableView() ;

Callback<Class<?>, Object> controllerFactory = controllerType -> {
    if (controllerType == MainController.class) {
        return new MainController(model);
    } else {
        throw new IllegalStateException("Unexpected controller class: "+controllerType.getName());
    }
};

FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
loader.setControllerFactory(controllerFactory);
Scene scene = new Scene(loader.load(), 1800, 900);
// ...

In this version, you keep the fx:controller attribute in the FXML file.

The basic idea here is that the controller factory is a function that maps the type of the controller to the controller instance, so you can use the controller factory to configure how the controller is created. You can use reflection to make this reusable (get the constructors for the class, check if any of them take a single parameter whose type is the model type, and invoke that constructor if so, otherwise just invoke the default constructor).

You can also use this technique to use a dependency injection framework such as Spring to create your controllers: see Dependency Injection and JavaFX. This, of course, means you can use Spring to inject the model into the controllers, which becomes very nice.

Finally, note that there is a JavaFX-specific dependency injection framework, afterburner.fx. This basically uses this technique under the hood to allow you to simply use javax.inject annotations directly in the controllers, so you just have to annotate the model in order to automatically inject it. If you have a medium-large scale application with lots of injection required, this is a very good option.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Okey, I understand the idea, but I should apply this method to every slingle one controller. Is that a proper way of doing that or it's just messing with code? – Michael213 Jul 26 '17 at 19:57
  • @Michael213 Added a little more info. It sounds like you might definitely want to consider integrating Spring, or using JavaFX. – James_D Jul 26 '17 at 21:00
  • I tried the solution which you explained to me (with passing models throw constructors) but compiler demands me to specify in fxml file controllers: Caused by: javafx.fxml.LoadException: No controller specified. – Michael213 Jul 27 '17 at 16:48
  • @Michael213 Yes, sorry: I skipped a line. Fixed now. – James_D Jul 27 '17 at 16:58