JavaFX Access a method from a nested Controller

Felix Kinyua :

I am experimenting with having different FXML files with different controllers. The AddBalances.fxml has fields to input text and then add to the database when the post button is pressed. It is then ideally supposed to refresh the Tableview to reflect the updated list from the database. This is supposed to be done by the sample.refresh() method. Currently, it updates the database but does not reload the data to refresh.

NB the sample.java controller (where I have nested other controllers), can access methods from the nested controllers just fine. i.e the bottomLabelController.checkConnection() and tableViewController.loadFinanceData() work as expected.

NB When I had one FXML file and one controller the TableView refreshed well.

I hope that is enough info.

Here is the error (caused by controller.refresh() in the AddBalancesController.

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1787)
    at javafx.fxml/javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1670)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Node.fireEvent(Node.java:8879)
    at javafx.controls/javafx.scene.control.Button.fire(Button.java:200)
    at javafx.controls/com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:206)
    at javafx.controls/com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3851)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.access$1200(Scene.java:3579)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1849)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2588)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:397)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:434)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:390)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:433)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:556)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:942)
    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$MethodHandler.invoke(FXMLLoader.java:1784)
    ... 47 more
Caused by: java.lang.NullPointerException
    at BudgetApp/sample.AddBalancesController.addBalances(AddBalancesController.java:65)
    ... 58 more

sample.fxml

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

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="1280.0"
            xmlns="http://javafx.com/javafx/11.0.1"
            xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="sample.Controller">
   <top>
      <fx:include source="menuBar.fxml"/>
   </top>
   <left>
      <VBox prefHeight="375.0" prefWidth="225.0" BorderPane.alignment="CENTER">
         <fx:include source="ViewBalances.fxml" fx:id="viewBalances"/>
         <fx:include source="AddBalances.fxml" fx:id="addBalances"/>
         <BorderPane.margin>
            <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
         </BorderPane.margin>
      </VBox>
   </left>
   <center>
      <fx:include source="TableView.fxml" fx:id="tableView"/>
   </center>
   <bottom>
      <fx:include source="BottomLabel.fxml" fx:id="bottomLabel"/>
   </bottom>
</BorderPane>

Controller.java (Controller for sample.fxml)


    package sample;

import datasource.DbConnect;
import datasource.DbModel;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;



public class Controller implements Initializable {


    private DbConnect dbConnect;

    DbModel dbModel = new DbModel();


    @FXML
    private BottomLabelController bottomLabelController;
    @FXML
    private TableViewController tableViewController;
    @FXML
    private AddBalancesController addBalancesController;

    public void initialize(URL url, ResourceBundle rb){

        bottomLabelController.checkConnection();
        tableViewController.loadFinanceData();
    }

    public void refresh(){

        tableViewController.loadFinanceData();
    }

}

AddBalances.fxml


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


<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox prefHeight="403.0" prefWidth="225.0" xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      fx:controller="sample.AddBalancesController">
    <children>
        <Label prefHeight="17.0" prefWidth="220.0" text="Enter Balances">
            <VBox.margin>
                <Insets left="10.0" right="10.0" top="10.0"/>
            </VBox.margin>
        </Label>
        <DatePicker fx:id="datepicker" prefWidth="220.0" promptText="Date">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </DatePicker>
        <TextField fx:id="cashfield" promptText="Cash">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </TextField>
        <TextField fx:id="mpesafield" layoutX="10.0" layoutY="37.0" promptText="Mpesa">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </TextField>
        <TextField fx:id="bankfield" layoutX="10.0" layoutY="62.0" promptText="Bank">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </TextField>
        <TextField fx:id="stimafield" layoutX="10.0" layoutY="112.0" promptText="Stima Sacco">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </TextField>
        <TextField fx:id="field" layoutX="10.0" layoutY="137.0" promptText="Loan">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </TextField>
        <TextField fx:id="advancefield" layoutX="10.0" layoutY="162.0" promptText="Advance">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </TextField>
        <Button fx:id="postbutton" mnemonicParsing="false" onAction="#addBalances" prefHeight="25.0" prefWidth="229.0"
                text="Post">
            <VBox.margin>
                <Insets left="5.0" right="5.0" top="5.0"/>
            </VBox.margin>
        </Button>
    </children>
</VBox>

AddBalancesController.java (Controller for Addbalances.fxml)


package sample;

import datasource.DbConnect;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextField;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class AddBalancesController {

    @FXML
    private Controller controller;
    @FXML
    private TableViewController tableViewController;
    @FXML
    private DatePicker datepicker;
    @FXML
    private TextField cashfield;
    @FXML
    private TextField mpesafield;
    @FXML
    private TextField bankfield;
    @FXML
    private TextField stimafield;
    @FXML
    private TextField loanfield;
    @FXML
    private TextField advancefield;
    @FXML
    private Button postbutton;


    @FXML
    public void addBalances() {

        String sqlinsert = "INSERT INTO data(date, cash, mpesa, bank, stimasacco, loan, advance) VALUES (?, ?, ?, ?, ?, ?, ?)";
        try {

            Connection connection = DbConnect.getConnection();
            PreparedStatement insertPreparedStatement = connection.prepareStatement(sqlinsert);

            insertPreparedStatement.setString(1, this.datepicker.getEditor().getText());
            insertPreparedStatement.setString(2, this.cashfield.getText());
            insertPreparedStatement.setString(3, this.mpesafield.getText());
            insertPreparedStatement.setString(4, this.bankfield.getText());
            insertPreparedStatement.setString(5, this.stimafield.getText());
            insertPreparedStatement.setString(6, this.loanfield.getText());
            insertPreparedStatement.setString(7, this.advancefield.getText());


            insertPreparedStatement.execute();

            this.datepicker.setValue(null);
            this.cashfield.setText("");
            this.mpesafield.setText("");
            this.bankfield.setText("");
            this.stimafield.setText("");
            this.loanfield.setText("");
            this.advancefield.setText("");

            controller.refresh();

            connection.close();

        } catch (SQLException e) {

            System.out.println("addBalances() " + e.getMessage());

        }

    }

}

TableView.fxml


    <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.BorderPane?>


<TableView fx:id="financeDataTableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"
           xmlns="http://javafx.com/javafx"
           xmlns:fx="http://javafx.com/fxml"
           fx:controller="sample.TableViewController">
    <columns>
        <TableColumn fx:id="date" prefWidth="75.0" text="Date"/>
        <TableColumn fx:id="cashinwallet" prefWidth="75.0" text="Cash in Wallet"/>
        <TableColumn fx:id="mpesa" prefWidth="75.0" text="Mpesa"/>
        <TableColumn fx:id="bank" prefWidth="75.0" text="Bank"/>
        <TableColumn fx:id="stimasacco" prefWidth="75.0" text="Stima Sacco"/>
        <TableColumn fx:id="loan" prefWidth="93.0" text="Loan"/>
        <TableColumn fx:id="advance" prefWidth="93.0" text="Advance"/>
    </columns>
</TableView>

TableViewController.java (Controller for Tableview.fxml)

package sample;

import datasource.DbConnect;
import datasource.FinanceData;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;

public class TableViewController {

    @FXML
    private TableColumn<FinanceData, String> date;
    @FXML
    private TableColumn <FinanceData, String> cashinwallet;
    @FXML
    private TableColumn <FinanceData, String> mpesa;
    @FXML
    private TableColumn <FinanceData, String> bank;
    @FXML
    private TableColumn <FinanceData, String> stimasacco;
    @FXML
    private TableColumn <FinanceData, String> loan;
    @FXML
    private TableColumn <FinanceData, String> advance;
    @FXML
    private TableView<FinanceData> financeDataTableView;


    private ObservableList<FinanceData> financeDataObservableList;

    @FXML
    public void loadFinanceData(){

        try {
            Connection connection = DbConnect.getConnection();
            financeDataObservableList = FXCollections.observableArrayList();

            String sqlselect = "SELECT * FROM data";
            ResultSet resultSet = connection.createStatement().executeQuery(sqlselect);

            while (resultSet.next()) {

                financeDataObservableList.add(new FinanceData(resultSet.getString(1),
                        resultSet.getString(2), resultSet.getString(3),
                        resultSet.getString(4), resultSet.getString(5),
                        resultSet.getString(6), resultSet.getString(7)));
            }

            connection.close();
        } catch (SQLException | ParseException e) {

            System.out.println("loadFinanceData(): " + e.getMessage());
        }



        try {
            date.setCellValueFactory(new PropertyValueFactory<>("datefield"));
            cashinwallet.setCellValueFactory(new PropertyValueFactory<>("cashfield"));
            mpesa.setCellValueFactory(new PropertyValueFactory<>("mpesafield"));
            bank.setCellValueFactory(new PropertyValueFactory<>("bankfield"));
            stimasacco.setCellValueFactory(new PropertyValueFactory<>("stimafield"));
            loan.setCellValueFactory(new PropertyValueFactory<>("loanfield"));
            advance.setCellValueFactory(new PropertyValueFactory<>("advancefield"));

            financeDataTableView.setItems(null);
            financeDataTableView.setItems(financeDataObservableList);

        } catch (Exception e) {

            System.out.println("setcellvalue: " + e.getMessage());
        }

    }
}````


Abra :

As you wrote in your question...

Here is the error (caused by controller.refresh() in the AddBalancesController.

controller.refresh() is line 65 in file AddBalancesController.java and, according to the stack trace you posted, this line is throwing NullPointerException which means that controller is null.

controller is a member of class AddBalancesController and is annotated with @FXML. Here are the relevant code lines from class AddBalancesController:

@FXML
private Controller controller;

However AddBalances.fxml is a nested FXML of sample.fxml whose "controller" is an instance of class Controller. Here are the relevant excerpts from file sample.fxml:

fx:controller="sample.Controller">
.
.
<fx:include source="AddBalances.fxml" fx:id="addBalances"/>

And indeed addBalances is a member of class Controller...

@FXML
private AddBalancesController addBalancesController;

Looks to me like you think that the FXML loader will also initialize member controller in class AddBalancesController and that is where you are mistaken. In other words, you need to initialize member controller in class AddBalancesController, otherwise the member is null and hence the NullPointerException.

Probably the simplest way to initialize member controller in class AddBalancesController would be to add a method to class AddBalancesController, e.g.

public void setSampleController(Controller controller) {
    this.controller = controller;
}

Then in class Controller, call this method - probably in method initialize() of that class, as in:

public void initialize(URL url, ResourceBundle rb){
    bottomLabelController.checkConnection();
    tableViewController.loadFinanceData();
    addBalancesController.setSampleController(this); // I added this line.
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=335540&siteId=1