1

I am trying to build my first 'real' application with java using JavaFX. I'm using FXML to design the whole thing and so I decided to divide up some parts into different .fxml files and link those to different controllers. I'm using one MainController linked to main.fxml, and within main.fxml I call some other fxml files using fx:include. All includes have their own controller.

The issue is that the controllers don't operate 100% independently, sometimes you want to press a button linked to one controller and have that so something to another controller. I have a solution that works, but I'm not sure if it is the best way to do it.

My solution is this: Have one abstract SubController class, which has protected static fields for each of the subcontrollers. Upon initialization MainController fills all of these fields with the controller classes it gets from main.fxml, as well as one field for MainController itself. Each of the sub-controllers inherit from SubController so that they can access the static fields and call the public methods of the other controllers.

public abstract class SubController implements Initializable {

    protected static MainController mainController;
    protected static MenuBarController menuBarController;
    protected static VideoPanelController videoPanelController;

    public static void setMainController(MainController mainController) {
        SubController.mainController = mainController;
    }

    public static void setMenuBarController(MenuBarController menuBarController) {
        SubController.menuBarController = menuBarController;
    }

    public static void setVideoPanelController(VideoPanelController videoPanelController) {
        SubController.videoPanelController = videoPanelController;
    }
}
public class MainController implements Initializable {

    @FXML private MenuBarController menuBarController;
    @FXML private VideoPanelController videoPanelController;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {

        SubController.setMainController(this);
        SubController.setMenuBarController(menuBarController);
        SubController.setVideoPanelController(videoPanelController);

    }

This way when a button is clicked in the menu bar, it can use a public method of the video panel controller:

public class MenuBarController extends SubController {

    public void openVideo() {
        File selectedFile = chooseFiles(VIDEO_EXTENSIONS);
        if (selectedFile == null) return;
        videoPanelController.loadVideo(selectedFile);
    }

}

This works quite well, but I still feel like there's probably an easier way to do it. Is this a good way to go about it and if not, how else should I do it?

Sander
  • 325
  • 2
  • 8
  • Use call back functions. `MenuBarController` would have a `Consumer openVideoConsumer;`, and when a selected file is found, it will invoke the consumer & pass it the file: `openVideoConsumer.accept(selectedFile);`. Your `MainController` would wire the video panel to the menu: `menuBarController.setOpenVideoConsumer(videoPanelController::loadVideo);`. This way `MenuBarController` doesn't have to know of `VideoPanelController`, in the case you may want to change the menu to remove that option. – Vince Jun 19 '20 at 15:07
  • 1
    It's generally not good practice for controllers to communicate with each other at all. Use a MVC approach instead, where each controller communicates with a single model instance. See, for example, https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx/32343342#32343342. Using a dependency-injection framework can simplify the process of giving the controllers access to the model. – James_D Jun 19 '20 at 15:20
  • Another alternative is to use an event bus. If you're using Spring Framework, it already has one integrated. Otherwise, you could use something like [EventBus from Guava](https://github.com/google/guava/wiki/EventBusExplained). – Michel Jung Jun 19 '20 at 18:47
  • @James_D Thanks, I'll try to apply MVC to my program, that link was very useful to me. One last thing, using MVC, how should a Controller request to close the primary stage? I could store the primary stage in the model, but I feel like doing that kinda goes against the whole point of MVC, then the Model and the View aren't really separate entities anymore. – Sander Jun 20 '20 at 14:06
  • From any component (e.g. a `Button` or some kind of `Pane`) you can call `getScene().getWindow().hide()`. – James_D Jun 20 '20 at 14:17

0 Answers0