3

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.

  • think "model" not "function" or "ui": the data that is manipulated by the user has inherent logic/relationship that must be modeled in one or more data classes, those can be moved around and the view does nothing but listen to the changes of data properties or change them via dedicated api on the data objects – kleopatra Sep 17 '18 at 07:50
  • MVC concepts can be challenging for none advanced programmers. I am still in the process of trying to master it. This example by @James_D is a good one that gives a simple example to help developers understand one form of MVC. https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx – SedJ601 Sep 17 '18 at 14:06
  • Suppose you have a TabbedPane containing 10 tabs. Each tab contains different UI. In this case you dont have to control each and every UI element under several tabs via one controller. You would create 10 seperate fxml files each having controller class and then load each fxml file under tab when user changes tab. In this way you dont get a one huge controller – Umer Farooq Sep 18 '18 at 09:27

2 Answers2

1

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.

Bernhard
  • 1,253
  • 8
  • 18
  • Thank you for this detailed answers, explains the separaton of FXMLs nicely, managed to do it this way. Just having problems with the communication beween them. – Labib Kalil Shamon Sep 18 '18 at 13:51
1

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.

pdem
  • 3,880
  • 1
  • 24
  • 38
  • The Singleton suggestion sounds like an idea I had, having the Controller references in a static class (even though I actually didn't think of the Singleton Template. I looked into the afterburnerfx Framework, but wasn't sure how that'll help. My current structure is similar to what @Bernhard proposed. – Labib Kalil Shamon Sep 18 '18 at 13:54
  • AfterburnerFx is really simple, use @Inject annotation on a controller attribute that points to your singleton and call methods of Injector in the constructor of your controller and services like `Injector.registerExistingAndInject(this)` for services constructor and `Injector.injectMembers(getClass(),this)` for constroller constructor . The advantage is the automation of your services instanciaton and recursive instantiation (if a service uses a service) – pdem Sep 18 '18 at 14:22