0

I am trying to create a table view by referring to a few tutorials from YT. When i do a simple table view and its relevant controller it works.

Main Class:

    public class TableTest extends Application {

    public TableTest() {
        // TODO Auto-generated constructor stub
    }

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

    @Override
    public void start(Stage stage) throws Exception {
        try {
            // load the fxml file
            FXMLLoader tableloader = new FXMLLoader(getClass().getResource("TestTableView.fxml"));
            Parent root = tableloader.load();

            TableTestController controller = tableloader.getController();
            controller.initializeModel();

            //load the login scene
            Scene scene = new Scene(root, 800, 400);
            stage.setTitle("Test Table Screen");
            stage.setScene(scene);
            stage.show();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Controller Class :

public class TableTestController {

    @FXML private  TableView<TablePost> t_posts ;
    @FXML private  TableColumn<TablePost, String> c_post_id ;
    @FXML private  TableColumn<TablePost, String> c_post_title ;
    @FXML private  TableColumn<TablePost, String> c_post_desc ;
    @FXML private  TableColumn<TablePost, String> c_post_creator ;

    @FXML private TabPane TabPane;
    @FXML private Tab AllPosts;
    @FXML private AnchorPane t_anch;
    @FXML private TableTestController t_anchController;

    @FXML private Parent TAllPosts;

    public void initializeModel() {
        System.out.println("Starting Table Init");
        c_post_id.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postId"));
        c_post_title.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postTitle"));
        c_post_desc.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postDesc"));
        c_post_creator.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postCreator"));
        t_posts.setItems(getPosts());
        System.out.println("Finished Table Init");
   }

    public ObservableList<TablePost> getPosts() {
        ObservableList<TablePost> posts = FXCollections.observableArrayList();
        TablePost p1 ;
        p1 = new TablePost("id1","title1","desc1","creator1");
        posts.add(p1);
        p1 = new TablePost("id2","title2","desc2","creator2");
        posts.add(p1);
        return posts;
    }

}

TestTableView.fxml :

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

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity"
    minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
    prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1"
    xmlns:fx="http://javafx.com/fxml/1"
    fx:id ="t_anch"
    fx:controller="test.TableTestController">

    <children>
        <TableView fx:id="t_posts" layoutX="14.0" layoutY="65.0"
            prefHeight="141.0" prefWidth="570.0">
            <columns>
                <TableColumn fx:id="c_post_id" prefWidth="78.0"
                    text="Post Id" />
                <TableColumn fx:id="c_post_title" prefWidth="135.0"
                    text="Post Title" />
                <TableColumn fx:id="c_post_desc" prefWidth="237.0"
                    text="Post Desc" />
                <TableColumn fx:id="c_post_creator" prefWidth="119.0"
                    text="Post Creator" />
            </columns>
        </TableView>
    </children>
</AnchorPane>

All of this works , but the moment, I inject this fxml into a tab view I get NPE. i.e. if i change my line #28 in the Main class to refer to the new Tab based fxml it fails

This works : FXMLLoader tableloader = new FXMLLoader(getClass().getResource("TestTableView.fxml"));

This does not work : FXMLLoader tableloader = new FXMLLoader(getClass().getResource("TestTableView2.fxml"));

TestTableView2.fxml :

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

<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity"
    minHeight="-Infinity" minWidth="-Infinity" prefHeight="515.0"
    prefWidth="714.0" xmlns="http://javafx.com/javafx/11.0.1"
    xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="test.TableTestController">
    <center>
        <TabPane fx:id="TabPane">
            <tabs>
                <Tab fx:id="AllPosts" closable="false" text="All Posts">
                    <content>
                        <fx:include fx:id="TAllPosts"
                            source="TestTableView.fxml" />
                    </content>
                </Tab>
            </tabs>
        </TabPane>
    </center>
</BorderPane>

  • unrelated: java naming conventions please – kleopatra May 16 '20 at 03:34
  • 1
    Never use the same controller class for multiple FXML files. The relationship between a concrete controller class and an FXML file should be one-to-one. Doing otherwise makes it much harder to reason about which fields will be `null` at any given time; remember that an `FXMLLoader` will, by default, create a _new_ instance of the controller class specified by `fx:controller`. If you need to communicate between controller instances, whether they're of the same class or not, see [Passing Parameters JavaFX FXML](https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml). – Slaw May 16 '20 at 05:48
  • 1
    There's a different controller instance created for the fxml you add using `` The correct field name to get the controller instance for the included fxml injected would be `TAllPostsController` not`t_anchController`.Though you theoretically could use the same controller class for both fxmls,it's an extremely bad idea:One part of the injected fields would always be`null`for one of the fxmls while the rest of the injected fields is null for the other;this forces you to check, which fxml the controller is used with in multiple places.Sure you save yourself a few loc, but at what cost? – fabian May 16 '20 at 07:15
  • Also see the [nested controllers](https://openjfx.io/javadoc/14/javafx.fxml/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers) section in the documentation for handling controllers from included FXML files. – James_D May 16 '20 at 12:00
  • Thanks all - I did not get to work on this on the weekend. I will try these options and get back to you. – web profiler May 18 '20 at 06:46

2 Answers2

0

You can simply do the initialization in a controller for TableView2. Also add the AllPosts in the controller instead of the fxml:

public class TableView2Controller {

    @FXML private Tab AllPosts;

    @FXML
    public void initialize() throws IOException {

      FXMLLoader loader = new FXMLLoader(getClass().getResource("TestTableView.fxml"));
      Pane pane = loader.load();
      TableTestController controller = loader.getController();
      controller.initializeModel();
      AllPosts.setContent(pane);
   }
}
c0der
  • 18,467
  • 6
  • 33
  • 65
0

Thanks to suggestions from Slaw, Fabian and James_D and a few more hours pouring over internet tutorials I have now resolved this.

  1. I had to use nested controllers.
  2. The name of the sub fxml should match the name defined as fx:id in the main fxml. The controller for the sub fxml is auto injected if we specify it in the same format as the sub fxml name but adding Controller in the end and making the first letter lowercase. Refer my updated working code below.

My TestTableView.fxml is -

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity"
    minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
    prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1"
    xmlns:fx="http://javafx.com/fxml/1"
    fx:id ="t_anch"
    fx:controller="test.TestTableViewController">

    <children>
        <TableView fx:id="t_posts" layoutX="14.0" layoutY="65.0"
            prefHeight="141.0" prefWidth="570.0">
            <columns>
                <TableColumn fx:id="c_post_id" prefWidth="78.0"
                    text="Post Id" />
                <TableColumn fx:id="c_post_title" prefWidth="135.0"
                    text="Post Title" />
                <TableColumn fx:id="c_post_desc" prefWidth="237.0"
                    text="Post Desc" />
                <TableColumn fx:id="c_post_creator" prefWidth="119.0"
                    text="Post Creator" />
            </columns>
        </TableView>
    </children>
</AnchorPane>

My TestTableView2.fxml is -

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

<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity"
    minHeight="-Infinity" minWidth="-Infinity" prefHeight="515.0"
    prefWidth="714.0" xmlns="http://javafx.com/javafx/11.0.1"
    xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="test.TestTableView2Controller">
    <center>
        <TabPane>
            <tabs>
                <Tab closable="false" text="All Posts">
                    <content>
                            <fx:include fx:id="testTableView"
                                source="TestTableView.fxml" />
                    </content>
                </Tab>
            </tabs>
        </TabPane>
    </center>
</BorderPane>

TestTableViewController

package test;

import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import model.app.posts.TablePost;

public class TestTableViewController {

    @FXML private  TableView<TablePost> t_posts ;
    @FXML private  TableColumn<TablePost, String> c_post_id ;
    @FXML private  TableColumn<TablePost, String> c_post_title ;
    @FXML private  TableColumn<TablePost, String> c_post_desc ;
    @FXML private  TableColumn<TablePost, String> c_post_creator ;

    @FXML private TabPane TabPane;
    @FXML private Tab AllPosts;
    @FXML private AnchorPane t_anch;
    @FXML private TestTableViewController t_anchController;

    @FXML private Parent TAllPosts;

    @FXML
    public void initialize() {
        System.out.println("Starting Table Init");
        c_post_id.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postId"));
        c_post_title.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postTitle"));
        c_post_desc.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postDesc"));
        c_post_creator.setCellValueFactory(new PropertyValueFactory<TablePost, String>("postCreator"));
        System.out.println("Finished Table Init");
   }

    public void setTableItems(ObservableList<TablePost> posts) {
        t_posts.setItems(posts);
    }

}

TestTableView2Controller

package test;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import model.app.posts.TablePost;

public class TestTableView2Controller {

    @FXML private TestTableViewController testTableViewController;

    @FXML private void initialize() {
        ObservableList<TablePost> posts = FXCollections.observableArrayList();
        TablePost p1 ;
        p1 = new TablePost("id5","title1","desc1","creator1","OPEN");
        posts.add(p1);
        p1 = new TablePost("id6","title2","desc2","creator2","CLOSED");
        posts.add(p1);
        testTableViewController.setTableItems(posts);

    }

}

and lastly TableTest.java

package test;

import java.io.IOException;

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

public class TableTest extends Application {

    public TableTest() {
        // TODO Auto-generated constructor stub
    }

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

    @Override
    public void start(Stage stage) throws Exception {
        try {
            // load the fxml file
            FXMLLoader tableloader = new FXMLLoader(getClass().getResource("TestTableView2.fxml"));
            Parent root = tableloader.load();

            //load the login scene
            Scene scene = new Scene(root, 800, 400);
            stage.setTitle("Test Table Screen");
            stage.setScene(scene);
            stage.show();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}