0

Im trying to set the Text of a Textfield in my MainController from another file. I read that this:

FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
MainController mc = (MainController) loader.getController();

is how you're supposed to get a reference to the Controller Instance in Javafx. I tried to implement it, but it gives me a NullPointerException when trying to set the TextField. Here is my Code:

Main.java:

package application;

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


public class Main extends Application {

    static MainController mc;

    @Override
    public void start(Stage primaryStage) {
        try {
            Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
            Scene scene = new Scene(root,348,212);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Flextime calculator");
            primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("assets/icon.png")));
            primaryStage.setResizable(false);

            FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
            mc = (MainController) loader.getController();

            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

    public static MainController getMc() {
        return mc;
    }
}

MainController.java:

public class MainController {

    @FXML Label fxFlextime;
    @FXML Label fxTotalFlextime;
    @FXML TextField fxStarttime;
    @FXML TextField fxEndtime;
    @FXML TextField fxDailyWorktime;
    @FXML TextField fxBreaktime;
    @FXML TextField fxAddBreaktime;
    @FXML TextField fxBreakGap;
    @FXML CheckBox fxAddBreak;

    //Create Model, Userconfig
    Model model = new Model();
    UserConfig config = new UserConfig();

    public void initialize() {
        config.initialize();
    }
}

UserConfig.java:

public class UserConfig {

    public void initialize() {
        insertDefault();
    }

    void insertDefault() {
        List<String> prefValues = getPreference();
        //FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
        //MainController mc = (MainController) loader.getController();
        // ^Tried also putting it here to see wether im referencing the variable wrong or something,
        // so maybe im getting the Controller wrong? With mc.fxStarttime.setText("") it also didnt work here        
        Main.mc.fxStarttime.setText("");
        Main.mc.fxEndtime.setText(prefValues.get(2));
        Main.mc.fxDailyWorktime.setText(prefValues.get(3));
        Main.mc.fxBreaktime.setText(prefValues.get(4));
        Main.mc.fxAddBreaktime.setText(prefValues.get(5));
        Main.mc.fxBreakGap.setText(prefValues.get(6));

    }

    static List<String> getPreference() {
        // This will define a node in which the preferences can be stored
        Preferences userPrefs = Preferences.userNodeForPackage(Main.class);

        try {
            String[] keys = userPrefs.keys();

            if (keys == null) {
                userPrefs.put("DEFStarttime"    , "1");
                userPrefs.put("DEFEndtime"      , "");
                userPrefs.put("DEFDailyWorktime", "");
                userPrefs.put("DEFBreaktime"    , "");
                userPrefs.put("DEFAddBreaktime" , "");
                userPrefs.put("DEFBreakGap"     , "");
            }
        } catch (BackingStoreException ex) {
            System.err.println(ex);
        }

        List<String> prefValues = new ArrayList<String>();
        prefValues.add(userPrefs.get("DEFStarttime" , ""));
        prefValues.add(userPrefs.get("DEFEndtime"   , ""));
        prefValues.add(userPrefs.get("DEFDailyWorktime" , ""));
        prefValues.add(userPrefs.get("DEFBreaktime" , ""));
        prefValues.add(userPrefs.get("DEFAddBreaktime"  , ""));
        prefValues.add(userPrefs.get("DEFBreakGap" , ""));
        /*
        for(String string : prefValues) {
            System.out.println(string);
        }
        */
        return prefValues;
    }
}

The Error:

javafx.fxml.LoadException: 
/C:/eclipse-workspace/project/bin/application/main.fxml

    at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2595)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3237)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3194)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3163)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3136)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3113)
    at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:3106)
    at application.Main.start(Main.java:18)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    at java.base/java.lang.Thread.run(Thread.java:830)
Caused by: java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:76)
    at jdk.internal.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at javafx.base/com.sun.javafx.reflect.MethodUtil.invoke(MethodUtil.java:273)
    at javafx.fxml/com.sun.javafx.fxml.MethodHelper.invoke(MethodHelper.java:83)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2591)
    ... 17 more
Caused by: java.lang.NullPointerException
    at application.UserConfig.insertDefault(UserConfig.java:25)
    at application.UserConfig.initialize(UserConfig.java:18)
    at application.MainController.initialize(MainController.java:39)
    ... 28 more

I tried to cut it down to the minimal amount of code.

Edit: Here the Main.fxml:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.VBox?>

<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="212.0" prefWidth="348.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
   <children>
      <MenuBar fx:id="fxMenuBar">
        <menus>
          <Menu fx:id="fxFileMenu" mnemonicParsing="false" text="File">
            <items>
              <MenuItem fx:id="fxExitItem" mnemonicParsing="false" onAction="#exitProgram" text="Exit" />
            </items>
          </Menu>
          <Menu mnemonicParsing="false" text="Settings">
            <items>
              <MenuItem mnemonicParsing="false" text="About" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
      <FlowPane alignment="CENTER_LEFT" prefHeight="25.0" prefWidth="403.0">
         <children>
            <Label alignment="CENTER" prefHeight="15.0" prefWidth="65.0" text="Starttime:" underline="true">
               <FlowPane.margin>
                  <Insets left="10.0" top="10.0" />
               </FlowPane.margin>
            </Label>
            <TextField fx:id="fxStarttime" alignment="CENTER" prefHeight="25.0" prefWidth="45.0" promptText="00:00">
               <FlowPane.margin>
                  <Insets top="10.0" />
               </FlowPane.margin></TextField>
            <Label alignment="CENTER" prefHeight="17.0" prefWidth="112.0" text="Required daily hours:" underline="true">
               <FlowPane.margin>
                  <Insets left="10.0" right="5.0" top="10.0" />
               </FlowPane.margin>
            </Label>
            <TextField fx:id="fxDailyWorktime" alignment="CENTER" prefHeight="25.0" prefWidth="45.0" promptText="00.00">
               <FlowPane.margin>
                  <Insets top="10.0" />
               </FlowPane.margin></TextField>
         </children>
      </FlowPane>
      <FlowPane alignment="CENTER_LEFT" prefHeight="36.0" prefWidth="300.0">
         <children>
            <Label alignment="CENTER" prefHeight="15.0" prefWidth="65.0" text="Endtime:" underline="true">
               <FlowPane.margin>
                  <Insets left="10.0" />
               </FlowPane.margin>
            </Label>
            <TextField fx:id="fxEndtime" alignment="CENTER" prefHeight="25.0" prefWidth="45.0" promptText="00:00" />
            <Label alignment="CENTER" prefHeight="15.0" prefWidth="65.0" text="Breaktime:" underline="true">
               <FlowPane.margin>
                  <Insets left="57.0" right="5.0" />
               </FlowPane.margin>
            </Label>
            <TextField fx:id="fxBreaktime" alignment="CENTER" prefHeight="25.0" prefWidth="45.0" promptText="00" />
         </children>
      </FlowPane>
      <FlowPane prefHeight="38.0" prefWidth="300.0">
         <children>
            <CheckBox fx:id="fxAddBreak" mnemonicParsing="false" text="Add:">
               <FlowPane.margin>
                  <Insets left="20.0" top="5.0" />
               </FlowPane.margin>
            </CheckBox>
            <TextField fx:id="fxAddBreaktime" alignment="CENTER" prefHeight="25.0" prefWidth="45.0" promptText="00">
               <FlowPane.margin>
                  <Insets left="10.0" right="5.0" top="5.0" />
               </FlowPane.margin>
            </TextField>
            <Label text="breaktime from ">
               <FlowPane.margin>
                  <Insets top="5.0" />
               </FlowPane.margin>
            </Label>
            <TextField fx:id="fxBreakGap" alignment="CENTER" prefHeight="25.0" prefWidth="45.0" promptText="00.00">
               <FlowPane.margin>
                  <Insets top="5.0" />
               </FlowPane.margin>
            </TextField>
            <Label text="working hours">
               <FlowPane.margin>
                  <Insets left="5.0" top="5.0" />
               </FlowPane.margin>
            </Label>
         </children>
      </FlowPane>
      <FlowPane alignment="CENTER_RIGHT" prefHeight="32.0" prefWidth="300.0">
         <children>
            <Label text="Flextime:">
               <FlowPane.margin>
                  <Insets right="10.0" />
               </FlowPane.margin>
            </Label>
            <Label fx:id="fxFlextime" alignment="CENTER" prefHeight="10.0" prefWidth="35.0" text="0.00" textAlignment="CENTER">
               <FlowPane.margin>
                  <Insets />
               </FlowPane.margin>
            </Label>
            <Label text="h">
               <FlowPane.margin>
                  <Insets right="10.0" />
               </FlowPane.margin>
            </Label>
         </children>
      </FlowPane>
      <FlowPane alignment="CENTER_RIGHT" prefHeight="32.0" prefWidth="300.0">
         <children>
            <Label text="Total flextime:" underline="true">
               <FlowPane.margin>
                  <Insets right="10.0" />
               </FlowPane.margin>
            </Label>
            <Label fx:id="fxTotalFlextime" alignment="CENTER" prefHeight="10.0" prefWidth="35.0" text="0.00" textAlignment="CENTER">
               <FlowPane.margin>
                  <Insets />
               </FlowPane.margin>
            </Label>
            <Label text="h">
               <FlowPane.margin>
                  <Insets right="10.0" />
               </FlowPane.margin>
            </Label>
         </children>
      </FlowPane>
      <FlowPane alignment="CENTER_RIGHT" prefHeight="38.0" prefWidth="300.0">
         <children>
            <Button mnemonicParsing="false" onAction="#calculate" text="Calculate" underline="true">
               <FlowPane.margin>
                  <Insets right="10.0" />
               </FlowPane.margin>
            </Button>
            <Button mnemonicParsing="false" onAction="#addToTotal" text="Add to total" />
            <Button mnemonicParsing="false" onAction="#clear" text="Clear">
               <FlowPane.margin>
                  <Insets left="10.0" right="10.0" />
               </FlowPane.margin>
            </Button>
         </children>
      </FlowPane>
   </children>
</VBox>
Elekam
  • 75
  • 1
  • 9
  • Can you share the source of `main.fxml` – Thecarisma Feb 27 '20 at 08:34
  • @Thecarisma Done. In the main.fxml are mentioned a lot of methods, but these work all perfectly fine so I didn't mention them in the Code I gave. I can of course just add the whole files if necessary, I just thought it would be easier to read then. – Elekam Feb 27 '20 at 08:39
  • 3
    you must load the fxml before its controller is available – kleopatra Feb 27 '20 at 09:13
  • 2
    See this answer for the flow of FXMLLoader https://stackoverflow.com/a/40247181/6626422 – Thecarisma Feb 27 '20 at 09:23
  • 2
    This just seems mis-designed. Your `UserConfig` class shouldn't be referencing the controller at all. Just define methods in the `UserConfig` class to return the data (`getStartTime()`, `getEndTime()`, etc). Move the initialization code (reading the preferences and resorting to defaults if that's unavailable, currently in your `static` (why??) `getPreference()` method) to the constructor. Then in the controller's `initialize()` method create a `UserConfig` instance and initialize the fields by calling those `getStartTime()` methods, etc. – James_D Feb 27 '20 at 12:35
  • @James D I was trying to assemble all Logic related to `UserConfig` inside this separate Class. Do you mean I should only have methods which return, clear, ... preferences inside `UserConfig`, while methods like `setPreferences()` which need to access the TextFields should be inside `MainController`? – Elekam Feb 27 '20 at 13:09
  • @MaxK. Yes, that's what I mean. Setting the value of the text field is not logic related to `UserConfig`, so it shouldn't be in that class. – James_D Feb 27 '20 at 14:00
  • @James_D Okay, I thought because of `userPrefs.put("DEFStarttime", fxStarttime.getText());` that its related to `UserConfig` (because the config gets edited) and should therefore be in it. But I see now that even though this has to do with the UserConfigs, that its better to reside in the `Maincontroller` because it needs to access the `MainController`, which is harder to access than the userPrefernces. Correct? – Elekam Feb 27 '20 at 14:20
  • @MaxK. Yeah, kind of. There are a couple of OO principles and design patterns. The principle of single responsibility, which basically says each class should only be responsible for one thing, would dictate that the `UserConfig` class shouldn't be responsible for anything about the UI. The controller has a dependence on the `UserConfig`, because it needs to know what to display, but not the other way around. – James_D Feb 27 '20 at 15:26
  • And then you can also think of this in terms of the Model-View-Controller (MVC) design pattern. In that context, your `UserConfig` is your model (represents the data). It shouldn't know anything about the UI or the controller. Have a look at https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx/32343342#32343342 – James_D Feb 27 '20 at 15:28

1 Answers1

1

The reason why you are getting the NullPointerException is because the Main.mc is null at the point where you are trying to access it fields in UserConfig.

In the line where you load your main.fxml Parent root = FXMLLoader.load(getClass().getResource("main.fxml")); the initialize() method in the controller MainController is invoked and at that point you are invoking userConfig.initialize() which also make a backward reference to Main.mc which at that point is null.

You can make the config public in your MainController class and initialize user config after you set the mc field in the Main class. Or you can inject the MainController as parameter into the UserConfig initialize method.

Main.java

FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
Parent root = (Parent) loader.load();
Scene scene = new Scene(root,348,212);  
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("Flextime calculator");
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("assets/icon.png")));
primaryStage.setResizable(false);

mc = (MainController) loader.getController();
mc.config.initialize();

MainController.java

public UserConfig config = new UserConfig();

@FXML
public void initialize() {
    //remove the config.initialize(); from here
}
Thecarisma
  • 1,424
  • 18
  • 19