0

Departments controllers are loaded from SceneWithButtonsController. StageAddController is loaded from LayoutWithHomeAndAddController. I want to execute this method from StageAddController which executes corresponding method from departments controller :

//Show Stage to add a worker

public class LayoutWithHomeAndAddController {
    @FXML
    private void addButtonClicked() throws IOException{
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("/views/StageAdd.fxml"));
        BorderPane addWorkerPane = loader.load();

        StageAddController stageAddController = loader.getController();


        Stage stageAdd = new Stage();
        stageAdd.setTitle("Add New Worker");
        stageAdd.resizableProperty().setValue(Boolean.FALSE);
        Scene scene = new Scene(addWorkerPane);
        scene.getStylesheets().add("/css/redBorder.css");
        stageAdd.setScene(scene);
        stageAdd.initModality(Modality.WINDOW_MODAL);
        Stage mainStage = (Stage) root.getScene().getWindow();
        stageAdd.initOwner(mainStage);
        stageAdd.show();}
}

Controller class where departments are loaded:

public class SceneWithButtonsController {


//Show workers from Mechanical Department
   @FXML
    private void mechanicalDepartmentButtonClicked() throws IOException {
       FXMLLoader loader = new FXMLLoader();
       loader.setLocation(Main.class.getResource("/views/MechanicalDepartment.fxml"));
       BorderPane mehOdjel = loader.load();
       MechanicalDepartmentController mechanicalDepartmentController = loader.getController();
       mechanicalDepartmentController.populateTable();
       BorderPane borderPane = (BorderPane) pane.getParent();
       borderPane.setCenter(mehOdjel);

    }

    //Show workers from Electrical Department
    @FXML
    private  void electricalDepartmentButtonClicked() throws IOException {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("/views/ElectricalDepartment.fxml"));
        BorderPane elOdjel = loader.load();
        ElectricalDepartmentController electricalDepartmentController = loader.getController();
        electricalDepartmentController.populateTable();
        BorderPane borderPane = (BorderPane) pane.getParent();
        borderPane.setCenter(elOdjel);

    }
}

StageAddController:

public class StageAddController {

private ElectricalDepartmentController electricalDepartmentController;
    private MechanicalDepartmentController mechanicalDepartmentController;

    public void setElectricalDepartmentController(ElectricalDepartmentController electricalDepartmentController) {
        this.electricalDepartmentController = electricalDepartmentController;
    }

    public void setMechanicalDepartmentController(MechanicalDepartmentController mechanicalDepartmentController) {
        this.mechanicalDepartmentController = mechanicalDepartmentController;
    }


public void refreshTableOnSaveButtonClicked() {
    if (departmentBox.getValue().equals("Electrical")) {        
        electricalDepartmentController.refreshButtonClicked();
    }
    else if(departmentBox.getValue().equals("Mechanical")) {
        mechanicalDepartmentController.refreshButtonClicked();
    }
}
}

This means that refreshButtonClicked() method from ElectricalDepartmentController or MechanicalDepartmentController will be executed.

ElectricalDepartmentController (same is with MechanicalDepartmentController):

public class ElectricalDepartmentController {

public void initialize() throws IOException {
        StageAddController stageAddController = new StageAddController();
        stageAddController.setElectricalDepartmentController(this);
        workersTableColumn.setCellValueFactory(new PropertyValueFactory<>("nameSurname"));
        rowSelected();
    }
 @FXML
    public void populateTable() {
        for(Worker worker : workerDao.getWorkersNameSurname("Electrical")) workersList.addAll(worker);
        workersTable.setItems(workersList);
    }

@FXML
    public void refreshButtonClicked() {
        workersList.removeAll(workersList);
        populateTable();
    }
}

How to achieve this.

Kvark900
  • 147
  • 1
  • 4
  • 18

1 Answers1

0

You need to link controllers. In order to do that, when loading the respective FXML for the subordinate form, you have to set the parent controller for it manually so that you can have access to it.

Unfortunately, FXML does not provide a native way to link controllers. What you want to do is you want to use the full FXMLLoader functionality (instead of using FXMLLoader.load()), since the FXMLLoader also has a getController() method. It's probably a wise idea for your controllers to implement something like this:

public interface HierarchicalController {
    HierarchicalController getParentController();
    void setParentController(HierarchicalController controller);
}

EDIT: Component preload code:

public class SceneWithButtonsController {

   protected FXMLLoader<?> mechanicalLoader;
   protected FXMLLoader<?> electricalLoader;
   protected BorderPane mechanicalPane;
   protected BorderPane electricalPane;

   public void initialize() {
       mechanicalLoader = new FXMLLoader(Main.class.getResource("/views/MechanicalDepartment.fxml"));
       mechanicalPane = loader.load();
       MechanicalDepartmentController mechanicalDepartmentController = loader.getController();
       mechanicalDepartmentController.populateTable();

       electricalLoader = new FXMLLoader(Main.class.getResource("/views/MechanicalDepartment.fxml"));
       electricalPane = loader.load();
       ElectricalDepartmentController electricalDepartmentController = loader.getController();
       electricalDepartmentController.populateTable();

       // now what is the relation between StageAddController and SceneWithButtonsController ?

   }

   //Show workers from Mechanical Department
   @FXML
    private void mechanicalDepartmentButtonClicked() throws IOException {
       BorderPane borderPane = (BorderPane) pane.getParent();
       borderPane.setCenter(mechanicalPane);

    }

    //Show workers from Electrical Department
    @FXML
    private  void electricalDepartmentButtonClicked() throws IOException {
        BorderPane borderPane = (BorderPane) pane.getParent();
        borderPane.setCenter(electricalPane);

    }
}
Piotr Wilkin
  • 3,446
  • 10
  • 18
  • Can you please show that in an example. I tried lot of stuff with `loader.getController` but it gives me an exeption on calling the `refreshButtonClicked()` method. – Kvark900 Nov 17 '17 at 11:17
  • Sure, as a matter of fact, just did a sample of that with my students yesterday :) see https://github.com/pwilkin/ppa1/blob/plansze/src/sample/Plansze.java and https://github.com/pwilkin/ppa1/blob/plansze/src/sample/Plansza1.java – Piotr Wilkin Nov 17 '17 at 11:18
  • Can I use initialize method, can you please take a look at updated question? – Kvark900 Nov 17 '17 at 11:31
  • No, you can't. The reason for this is that FXML creates its controller by reflection. The controller that you manually create by using `new StageAddController();` is not the same controller object that the one created by `FXMLLoader`. If you want to pass something to **the** controller (i.e. the one actually associated with the loaded form), you have to use `loader.getController()` at load time. – Piotr Wilkin Nov 17 '17 at 11:36
  • StageAdd and Departments are loaded in the different classes. When loading StageAdd I need to have access to departments loaders to get theirs controllers. How to achieve that – Kvark900 Nov 17 '17 at 11:43
  • Without seeing the architecture of your forms (the .fxml files) it's hard to tell. You can't access loaders in advance, you can only do that at the very moment at which you actually add the subordinate .fxml generated component to your layout. I'd have to see the code that actually adds the layout elements. If you're not adding an element dynamically and you just want a separate controller for your embedded element, that you might want to use the `` directive, as per https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers – Piotr Wilkin Nov 17 '17 at 11:52
  • Oh, and if you do use ``, then indeed you can link the controllers in `initialize()`. – Piotr Wilkin Nov 17 '17 at 11:54
  • Okay, so this: `stageAddController.setElectricalDepartmentController(this);` should go into `LayoutWithHomeAndAddController::addButtonClicked()` (after `loader.load()`) instead of where it is now. – Piotr Wilkin Nov 17 '17 at 12:12
  • This class is not ElectricalDepartmentController, see setElectricalDepartmentController() method... – Kvark900 Nov 17 '17 at 12:19
  • Ah, I see. Then it looks like a case of you having to preload the two components in advance in the `initiialize()` method of the `StageAddController`, keep the respective loaders as fields in the `StageAddController` and mutually initialize the subcontrollers. – Piotr Wilkin Nov 17 '17 at 12:34
  • This kind of design is going to get pretty complex, and consequently hard to understand and maintain. Instead of all the controllers having references to each other, consider using a model class and sharing an instance of it with each controller. If you implement the table contents using `ObservableList`s and keep those lists in the model, the appropriate controllers can just observe the lists and table updates should be essentially transparent. You can also consider using a dependency injection framework to make it easier to share the model instance with the controllers. – James_D Nov 17 '17 at 12:43
  • Added together with question. – Piotr Wilkin Nov 17 '17 at 12:44
  • @James_D any sort of synchronization is generally hard to understand and maintain. Especially dependency injection introduces a lot of logical overhead. A domain-driven architecture is nice, but not always feasible, since not all synchronization actions between controllers are data-driven. When the infrastructure becomes really cumbersome, the usual solution is to provide some sort of central instance that manages it all - be it a domain model, an event bus or a service locator. What it should be really depends on the application in question. – Piotr Wilkin Nov 17 '17 at 12:54
  • @PiotrWilkin Understood, but in this case all that is happening is that the synchronization (apparently) needs to happen because the contents of one of more lists change. JavaFX is designed for this to happen automatically, as long as you share and modify the `ObservableList`s. So I think the "shared object" is pretty clear in this case, no? – James_D Nov 17 '17 at 13:01
  • @James_D Can you please show example with model class? – Kvark900 Nov 17 '17 at 13:05
  • @Kvark900 https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx/32343342#32343342 – James_D Nov 17 '17 at 13:09