0

I'm trying to write a GUI application in JavaFX. Basic idea is that there is a main GridPane view, and inside it - another Pane that should be changed on a button press. Since this Pane will have to read some input from user, I wanted there to be flow between Controllers so that I could pass data between different parts of the app. My idea was to keep all controllers in one place and refer to their fields from any place. It doesn't seem to be working - it seems like I cannot modify the content of a Pane from outside of it, even though I have access to its fields. It looks more or less like this:

main_view.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>

<GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.MainController">
   <columnConstraints>
      <ColumnConstraints />
   </columnConstraints>
   <rowConstraints>
      <RowConstraints />
   </rowConstraints>
   <children>
      <GridPane prefHeight="300.0" prefWidth="400.0">
        <columnConstraints>
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="30.0" prefWidth="100.0" />
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="20.0" GridPane.rowSpan="2">
               <children>
                  <TextField fx:id="userInputTextField">
                     <VBox.margin>
                        <Insets left="5.0" right="5.0" />
                     </VBox.margin>
                  </TextField>
                  <Button mnemonicParsing="false" onAction="#appendToFirstArea" text="Append to area 1" />
                  <Button mnemonicParsing="false" onAction="#appendToSecondArea" text="Append to area 2" />
                  <Button mnemonicParsing="false" onAction="#moveToFirstArea" text="Move to area 1" />
                  <Button mnemonicParsing="false" onAction="#moveToSecondArea" text="Move to area 2" />
               </children>
               <GridPane.margin>
                  <Insets />
               </GridPane.margin>
            </VBox>
            <Pane fx:id="content" prefHeight="300.0" prefWidth="280.0" GridPane.columnIndex="1" GridPane.rowSpan="2" />
         </children>
      </GridPane>
   </children>
</GridPane>

MainController.java

package main;

import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;

import java.io.IOException;

public class MainController {
    public TextField userInputTextField;
    public Pane content;

    private FirstAreaController getFirstAreaController() {
        return Main.firstAreaController;
    }

    private SecondAreaController getSecondAreaController() {
        return Main.secondAreaController;
    }

    public void appendToFirstArea(ActionEvent actionEvent) {
        String text = userInputTextField.getText();
        getFirstAreaController().firstTextArea.appendText(text + "\n");
    }

    public void appendToSecondArea(ActionEvent actionEvent) {
        String text = userInputTextField.getText();
        getSecondAreaController().secondTextArea.appendText(text + "\n");
    }

    public void moveToFirstArea(ActionEvent actionEvent) {
        try {
            GridPane firstArea = FXMLLoader.load(getClass().getResource("first_area.fxml"));
            content.getChildren().clear();
            content.getChildren().add(firstArea);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void moveToSecondArea(ActionEvent actionEvent) {
        try {
            GridPane secondArea = FXMLLoader.load(getClass().getResource("second_area.fxml"));
            content.getChildren().clear();
            content.getChildren().add(secondArea);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

first_area.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane onKeyPressed="#appendTest" maxHeight="-Infinity" maxWidth="-Infinity"
          minHeight="-Infinity" minWidth="-Infinity"
          prefHeight="300.0" prefWidth="280.0"
          xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.111"
          fx:controller="main.FirstAreaController">
    <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
    </columnConstraints>
    <rowConstraints>
        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
    </rowConstraints>
    <children>
        <TextArea fx:id="firstTextArea" prefHeight="200.0" prefWidth="200.0">
            <GridPane.margin>
                <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
            </GridPane.margin>
        </TextArea>
    </children>
</GridPane>

FirstAreaController.java

package main;

import javafx.scene.control.TextArea;

public class FirstAreaController {
    public TextArea firstTextArea;
}

second_area.fxml

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane maxHeight="-Infinity" maxWidth="-Infinity"
          minHeight="-Infinity" minWidth="-Infinity"
          prefHeight="300.0" prefWidth="280.0"
          xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.111"
          fx:controller="main.SecondAreaController">
    <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
    </columnConstraints>
    <rowConstraints>
        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
    </rowConstraints>
    <children>
        <TextArea fx:id="secondTextArea" prefHeight="200.0" prefWidth="200.0">
            <GridPane.margin>
                <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
            </GridPane.margin>
        </TextArea>
    </children>
</GridPane>

SecondAreaController.java

package main;

import javafx.scene.control.TextArea;

public class SecondAreaController {
    public TextArea secondTextArea;
}

Main.java

package main;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    public static MainController mainController;
    public static FirstAreaController firstAreaController;
    public static SecondAreaController secondAreaController;

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("main_view.fxml"));
        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 400, 300));
        mainController = loader.getController();

        FXMLLoader firstLoader = new FXMLLoader();
        firstLoader.setLocation(getClass().getResource("first_area.fxml"));
        firstLoader.load();
        firstAreaController = firstLoader.getController();

        FXMLLoader secondLoader = new FXMLLoader();
        secondLoader.setLocation(getClass().getResource("second_area.fxml"));
        secondLoader.load();
        secondAreaController = secondLoader.getController();

        primaryStage.setResizable(false);
        primaryStage.show();
    }

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

After I press "Append to 1 area" button nothing happens - the text doesn't show up in the TextArea. Though when I add a method that is called only inside FirstAreaController text appears in the area just as expected. How can I fix it?

shooqie
  • 950
  • 7
  • 17

1 Answers1

1

You're loading second_area.fxml twice: once in MainController, where you load second_area.fxml and display it, and once in Main. The controller you are accessing is the controller associated to the UI you load when you load second_area.fxml in Main, but the UI you actually display is the one you load in MainController. So when you call methods on the controller, you are updating a UI you never displayed.

The "global data" approach with public fields all over the place is not a particularly good idea anyway, and (for many well-documented reasons) is unlikely to be very maintainable in the long run.

Since you're only accessing these from MainController, just make the FirstAreaController and SecondAreaController fields in the MainController class, and set their values when you load (and display) the corresponding FXML files:

public class MainController {
    public TextField userInputTextField;
    public Pane content;

    private FirstAreaController firstAreaController ;
    private SecondAreaController secondAreaController ;



    public void appendToFirstArea(ActionEvent actionEvent) {
        String text = userInputTextField.getText();
        firstAreaController.firstTextArea.appendText(text + "\n");
    }

    public void appendToSecondArea(ActionEvent actionEvent) {
        String text = userInputTextField.getText();
        secondAreaController.secondTextArea.appendText(text + "\n");
    }

    public void moveToFirstArea(ActionEvent actionEvent) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("first_area.fxml"));
            GridPane firstArea = loader.load();
            firstAreaController = loader.getController();
            content.getChildren().clear();
            content.getChildren().add(firstArea);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void moveToSecondArea(ActionEvent actionEvent) {
        try {
            FXMLLoader loader = getClass().getResource("second_area.fxml");
            GridPane secondArea = loader.load();
            secondAreaController = loader.getController();
            content.getChildren().clear();
            content.getChildren().add(secondArea);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

If you need to update the UI from "more distant" objects, consider using a data model and an MVC approach. See, e.g. Applying MVC With JavaFx.

James_D
  • 201,275
  • 16
  • 291
  • 322