I'm relatively new to JavaFX but I try to be clear.
The issue: When my main controller calls my sub controller's function, it runs completely fine as long as it do not need to manipulate the fxml file's content.
Surprisingly, the code don't cause any error if the fxml annotation components are manipulated in the initialize function. But whenever I want to use them in other functions it will throw a NullPointerException.
So to be a bit clearer under fxml annotation components I mean for example this: @FXML private Label exampleLbl.
I created a simplified example:
Main Controller class:
package application;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Tab;
import javafx.scene.layout.AnchorPane;
public class MainController implements Initializable {
@FXML SubController subController;
@FXML private Tab Tab1;
@Override
public void initialize(URL location, ResourceBundle resources) {
subController = new SubController();
FXMLLoader loader = new FXMLLoader();
try {
AnchorPane anch1 = loader.load(getClass().getResource("SubView.fxml"));
Tab1.setContent(anch1);
}
catch(Exception e) {
System.out.println("unable to load tab1");
e.printStackTrace();
}
}
public void fooFunction() {
if(subController != null) {
subController.testFunction();
}
}
}
Sub controller class:
package application;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class SubController implements Initializable {
@FXML private Label exampleLbl;
@Override
public void initialize(URL arg0, ResourceBundle arg1) {
if(exampleLbl != null) {
System.out.println("It is printed, so it is not null");
exampleLbl.setText("So I can manipulate this as I want.");
}
}
public void testFunction() {
if(exampleLbl == null) {
System.out.println("It is printed, so it is null now.");
exampleLbl.setText("But not here. It will throw an error.");
}
}
}
The nullptrexception occurs on SubController's class in testFunction() method, line 26.
Line: exampleLbl.setText("But not here. It will throw an error.");
So it works fine in initialize function, but not in testFunction
Here are my FXML files:
MainView fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<center>
<TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab fx:id="Tab1" text="Test">
<content>
</content>
</Tab>
</tabs>
</TabPane>
</center>
<bottom>
<Button mnemonicParsing="false" onAction="#fooFunction" text="Button" BorderPane.alignment="CENTER" />
</bottom>
</BorderPane>
SubView FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SubController">
<Label fx:id="exampleLbl" layoutX="5.0" layoutY="128.0" prefHeight="17.0" prefWidth="188.0" text="Label" />
</AnchorPane>
Here is the error message and the console output if it helps:
It is printed, so it is not null
It is printed, so it is null now.
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at javafx.scene.control.Button.fire(Button.java:185)
at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$358(GlassViewEventHandler.java:432)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$152(WinApplication.java:177)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.Trampoline.invoke(Unknown Source)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1771)
... 48 more
Caused by: java.lang.NullPointerException
at application.SubController.testFunction(SubController.java:26)
at application.MainController.fooFunction(MainController.java:36)
... 58 more
So I would be thankful if someone could explain what am I missing here and provide me simple solution.
When you use subController = new SubController()
, the instance you just created has no relation to the FXML file. None of the fields will be injected because the controller instance was not managed by an FXMLLoader
. In your case, using the following should solve the problem:
@Override
public void initialize(URL location, ResourceBundle resources) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("SubView.fxml"));
try {
AnchorPane anch1 = loader.load();
subController = loader.getController();
Tab1.setContent(anch1);
} catch (Exception e) {
System.out.println("unable to load tab1");
e.printStackTrace();
}
}
Note two things:
FXMLLoader
during construction. When you were using loader.load(/* URL */)
you were actually using the static FXMLLoader#load(URL)
method. You need to use the instance load()
method.subController
field (which needn't be annotated with @FXML
, by the way) with loader.getController()
. That method will return the instance the FXMLLoader
created from the class name you specified via the fx:controller
attribute in the FXML file. Important: You must call getController()
after load()
.You may also want to consider using fx:include
.
MainView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<center>
<TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab fx:id="Tab1" text="Test">
<content>
<!-- replace the value of source as needed -->
<fx:include source="SubView.fxml" fx:id="sub"/>
</content>
</Tab>
</tabs>
</TabPane>
</center>
<bottom>
<Button mnemonicParsing="false" onAction="#fooFunction" text="Button" BorderPane.alignment="CENTER" />
</bottom>
</BorderPane>
This makes it no longer necessary to load the SubView.fxml
file manually inside the MainController#initialize
method. Also, since I assigned an fx:id
to the included file, the SubController
instance will be injected for you. When it comes to injecting controllers the FXMLLoader
looks for an appropriately typed (and annotated) field with the name <fx:id>Controller
(see Nested Controllers).