-1

I am trying to build a GUI in JavaFX using FXML. To do so, I have a main FXML page, which consists of 3 custom components that I also created using FXML. Here is my code directory:

src
   App.java
   ui
      MainView.java
      CustomHeader.java
      CustomBody.java
   fxml
      MainView.fxml
      CustomHeader.fxml
      CustomBody.fxml
   controller
      MainViewController.java
      CustomHeaderController.java
      CustomBodyController.java

App.java

package ui;

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import model.main.TaskItem;

import java.time.LocalDateTime;
import java.util.ArrayList;

public class ProductivityTrackerApp extends Application {
    public static Stage primaryStage;
    public static final String TITLE = "App";
    public static final double WIDTH = 520;
    public static final double HEIGHT = 800;

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

    public void setPrimaryStage(Stage primaryStage) {
        this.primaryStage = primaryStage;
    }

    public static void setScene(Parent root) {
        try {
            Scene scene = new Scene(root, WIDTH, HEIGHT);
            primaryStage.setTitle(TITLE);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (Exception e) {
            System.out.println("Failed to load new Scene!");
        }
    }
    @Override
    public void start(Stage primaryStage) throws Exception {
        setPrimaryStage(primaryStage);
        setScene(new MainView());
    }
}

Main.fxml

<fx:root xmlns="http://javafx.com/javafx"
         xmlns:fx="http://javafx.com/fxml"
         fx:controller="controllers.MainController"
         fx:id="root"
         type="VBox">

    <children>
         <CustomHeader> </CustomHeader>
         <CustomBody> </CustomBody>
    </children>
</fx:root>

MainView.java

package ui;

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.VBox;

import java.io.File;
import java.io.IOException;

public class MainView extends VBox {

    private static final String FXML = "src/fxml/MainView.fxml";
    private File fxmlFile = new File(FXML);

    public MainView() {
        this.load();
    };

    private void load() {
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(fxmlFile.toURI().toURL());
            fxmlLoader.setRoot(this);
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }
}

CustomHeader.fxml

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

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root xmlns="http://javafx.com/javafx"
         xmlns:fx="http://javafx.com/fxml"
         fx:controller="controllers.CustomHeaderController"
         minHeight="80.0" prefWidth="520.0" type="StackPane">

<BorderPane>
    <left>

    </left>

    <center>
        <Label text="App"/>
    </center>

    <right>
       <Button text="Click here" onMouseClicked="#colorBody"/>
    </right>
</BorderPane>
</fx:root>

CustomHeader.java

package ui;

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.StackPane;

import java.io.File;
import java.io.IOException;

public class MainView extends StackPane {

    private static final String FXML = "src/fxml/CustomHeader.fxml";
    private File fxmlFile = new File(FXML);

    public CustomHeader() {
        this.load();
    };

    private void load() {
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(fxmlFile.toURI().toURL());
            fxmlLoader.setRoot(this);
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }
}

CustomBody.fxml


<fx:root xmlns="http://javafx.com/javafx"
         xmlns:fx="http://javafx.com/fxml"
         fx:controller="controllers.CustomBodyController"
         type="StackPane">

   <Rectangle width="240" height="240" StackPane.alignment = "CENTER"/>
</fx:root>

CustomBody.java

package ui;

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.StackPane;

import java.io.File;
import java.io.IOException;

public class MainView extends StackPane {

    private static final String FXML = "src/fxml/CustomBody.fxml";
    private File fxmlFile = new File(FXML);

    public CustomBody() {
        this.load();
    };

    private void load() {
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(fxmlFile.toURI().toURL());
            fxmlLoader.setRoot(this);
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }
}

The controllers have no functionality yet. Hence, I haven't included them.

I want to introduce a feature such that an onClick action on the button in the CustomHeader component changes the color of the rectangle in the CustomBody component. However, since these components have separate controllers, I wonder if there is a way for me to access both of these controllers from the MainController (the controller of the parent component). Furthermore, if possible, are there any better suggestions for mediating the communication of two sibling components?

  • Perhaps a duplicate of: [Share model with nested controller](https://stackoverflow.com/questions/37068243/share-model-with-nested-controller) – jewelsea Dec 26 '19 at 21:47
  • [mcve] please .. – kleopatra Dec 26 '19 at 22:25
  • Normally u would do this with custom controllers and fx:include if u have a fx:id called "custom" u can have a variable in the parent Controller called customController in which the Controller is gonna be injected in – Alex Dec 27 '19 at 08:53
  • Atm u are using custom nodes which I would advice against(also works but not a good way to do it) – Alex Dec 27 '19 at 08:56
  • https://stackoverflow.com/questions/40911450/javafx-defining-custom-controls a little like that – Alex Dec 27 '19 at 08:58
  • https://github.com/FAForever/downlords-faf-client/blob/2ab525bcb6b9c0183c9b266fc9ccb4dd8636b10e/src/main/resources/theme/vault/vault.fxml#L29 there u can see example code – Alex Dec 27 '19 at 09:40
  • https://github.com/FAForever/downlords-faf-client/blob/2ab525bcb6b9c0183c9b266fc9ccb4dd8636b10e/src/main/java/com/faforever/client/vault/VaultController.java#L30 what it looks like in th parent Controller – Alex Dec 27 '19 at 09:42
  • @Alex is there a way to do this with custom nodes? The example above is a simplified version of the real thing I am working with, and that contains a lot more custom components. In order to maintain readability, I wonder if I can still use custom nodes? – hardik-ddod Dec 27 '19 at 16:42
  • Well add an fx:id and a variable in the controller named after the id and of the custom type i see no reason why that would not work differently with custom nodes than normal ones. – Alex Dec 27 '19 at 18:10
  • I disagree to usingcustom nodes tho. MVC forced by custom controllers instead of custom nodes is way better. Custom nodes are only needed if you wanna add your own UI component with new/different graphics. If you don't you should go for Controllers. – Alex Dec 27 '19 at 18:17
  • Added an code example, please consult! – hardik-ddod Dec 27 '19 at 18:30

1 Answers1

0
<CustomHeader fx:id="customHead"> </CustomHeader>

in the fxm file and add a variable

public CustomHead customHead;

in the MainController.

Alex
  • 521
  • 7
  • 17
  • I don't see how I can use this. If an action occurs in the CustomHeader component, how can I use it to change to CustomBody component? Maybe, the code example I added will make what I am trying to say clearer? – hardik-ddod Dec 27 '19 at 18:32
  • Still I advise u to use controllers instead of custo nodes – Alex Dec 28 '19 at 23:41
  • Just add fx:id to both CustomHeader and CustomBody. You can now call public methods on both Components – Alex Dec 28 '19 at 23:43