Splitting up JavaFX Controller Class

Viewed 1395

I do not have a MCV Code Example because the problem is more global and I don't think the specific code actually makes a big difference.

I have a GUI application with several Elements (datepickers, dropdown, entry fields, tabletreeview) and as per JavaFX one ControllerClass which got pretty big over the time.

Consequently I was looking into a way to split it up. In my research I found two main ways to do this:

Getting more of the program logic out of the UI part, which of course always makes sense and I did that for some parts, but for many it seems pretty unhandy, for example if I have a function that uses six different GUI elements it seems weird to extract that method out of the controller class, since I need to give it all those GUI elements references in the function call. Is there some better defaut way I'm missing? Like populating the Controller Class just with setters/getters and giving every method this as parameter so it can go this.dropbox1?

A second way I found was to split up the GUI itself into several scenes. Like this question, but I have no idea how to split up one GUI into several scenes and still have it in one window. I do realize that you can load other FXML files in your main-FXML file but I'm not sure how to combine them into a GUI in one window. If somebody had a little example code for this, I'd be grateful.

2 Answers

Welcome to Stackoverflow Labib, regarding the splitting up:

You can have a root FXML that contains for example just a GridPane(or sth else, but I'll continue on a GridPane premise), now you set up the Grid (without content) in that root FXML file, if you already have a finished GUI, you should have a clear idea on how big the grid-Cells need to be. In your main-Class you have:

Main Class that extends Application and launches GUI

public class GUI extends Application {

    private Stage stage;
    private GridPane rootGrid;

    @Override
    public void start(Stage stage) {

        this.stage = stage;

        // Load root layout from fxml file.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(WindowRoot.class.getResource("RootGrid.fxml"));
        rootGrid = (GridPane) loader.load();

        // Show the scene containing the root layout.
        Scene scene = new Scene(rootGrid);
        stage.setTitle("Test");
        stage.show();
        //fill the Grid
        setupGrid();
    }

Now you have rootGrid as class attribute which is just a grid with empty rows/columns as set up in the RootGrid.fxml, can be as simple as:

FXML example, two rows, two columns, no content

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

<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>


<GridPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141">
   <rowConstraints>
      <RowConstraints minHeight="10.0" prefHeight="30.0" />
      <RowConstraints minHeight="10.0" prefHeight="30.0" />
   </rowConstraints>
   <columnConstraints>
      <ColumnConstraints minWidth="10.0" prefWidth="100.0" />
      <ColumnConstraints minWidth="10.0" prefWidth="100.0" />
   </columnConstraints>
</GridPane>

Now you create FXML documents for every Cell you want to fill and in that FXML you have one AnchorPane with the contents of that Cell. Then you add it to the Grid as part of the start method. Let's say you want in that simple example a menubar. Create a FXML for the menu:

simple menu fxml example:

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

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141">
   <children>
      <MenuBar>
        <menus>
          <Menu mnemonicParsing="false" text="File">
            <items>
              <MenuItem mnemonicParsing="false" text="Close" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
   </children>
</AnchorPane>

And add it to the grid as part of the start method (but probably extract it into a method that you call in start, like setupGrid():

Setting up Grid, called in start(Stage stage)

private void setupGrid() {
    // Add Menubar
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(WindowRoot.class.getResource("menubar.fxml"));
    AnchorPane anchor = (AnchorPane) loader.load();
    //put it in first column, first row
    GridPane.setConstraints(anchor, 0, 0);
    //optional, let it span both/all columns
    GridPane.setRowSpan(anchor, 2);
    rootGrid.getChildren().add(anchor);

You'll need try/except IO blocks, left them out for better readability. For other "Root-Panes" you'll need other setup-methods, with a borderPane for example you'd have things like rootGrid.setTop(anchor).
Hope it helps. Have fun.

If you use FXML, the simple thing is to have a controller for a FXML (one to one) And personnaly I also add a css, it's a kind of pattern.

Then you have to split your screen in small parts: 2 methods:

  • using fx:include : you include a FXML wich itself declares a controller
  • using a component : You include the tag of your component which itself can load a FXML and a controller

Then you have to comunicate between your controllers, the best is to have a model layer, and to share it via singleton, using afterburnerfx for example(a very small injector)

When using fx:include, you may also use "nested controller" to get the reference of a child controller from ther parent.

Last your objects may be javaFX Beans with observable, and you only expose observable and not visual components from a controller to another: this way you have a loose coupling between your components.

Related