3

I am having a huge problem with TableView control in JavaFX. That's the first time I'm ever using it and I don't know what am I doing wrong.

My code:

Main class:

package sample;

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 {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

Controller class:

package sample;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableView;
import javafx.stage.Modality;
import javafx.stage.Stage;

import java.io.IOException;

public class Controller {

    @FXML
    private TableView<Person> peopleTableView;

    @FXML
    public void openNewWindow(){
        try {
            Parent root = FXMLLoader.load(getClass().getResource("mainWindow.fxml"));
            Stage mainWindow = new Stage();
            mainWindow.setTitle("App");
            mainWindow.setScene(new Scene(root, 1200, 600));
            mainWindow.initModality(Modality.APPLICATION_MODAL);
            mainWindow.show();

            ObservableList<Person> people = FXCollections.observableArrayList();

            people.add(new Person("Walter", "White"));
            people.add(new Person("Gus", "Fring"));

            peopleTableView.setItems(people);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Person class:

package sample;

import javafx.beans.property.SimpleStringProperty;

public class Person {

    private SimpleStringProperty firstName = new SimpleStringProperty("");
    private SimpleStringProperty lastName = new SimpleStringProperty("");

    public Person(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);
    }

    public String getFirstName() {
        return firstName.get();
    }

    public SimpleStringProperty firstNameProperty() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName.set(firstName);
    }

    public String getLastName() {
        return lastName.get();
    }

    public SimpleStringProperty lastNameProperty() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName.set(lastName);
    }
}

And two FXMLs codes, one for the window that shows up when we run the code:

sample.fxml:

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane fx:controller="sample.Controller"
            xmlns:fx="http://javafx.com/fxml">
    <center>
        <Button fx:id="openWindowButton" text="Open new window" onAction="#openNewWindow"/>
    </center>
</BorderPane>

And one for the new stage with TableView:

mainWindow.fxml:

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

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<?import javafx.scene.control.cell.PropertyValueFactory?>
<BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
    <center>
        <TableView fx:id="peopleTableView">
            <columns>
                <TableColumn text="First Name">
                    <cellValueFactory>
                        <PropertyValueFactory property="firstName"/>
                    </cellValueFactory>
                </TableColumn>
                <TableColumn text="Last Name">
                    <cellValueFactory>
                        <PropertyValueFactory property="lastName"/>
                    </cellValueFactory>
                </TableColumn>
            </columns>
        </TableView>
    </center>
</BorderPane>

So the issue is - I can't set items of TableView, because I get an exception that it is null even though it clearly shows up in the new stage which (I believe) it actually exists.

Error code:

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(Native Method)
    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:829)
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:566)
    at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:76)
    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:566)
    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 TableView.issue/sample.Controller.openNewWindow(Controller.java:35)
    ... 59 more
MrJ_
  • 55
  • 7
  • 1
    Create and post a [mre]. – James_D Jun 24 '21 at 11:11
  • @James_D hi, I see. In this case I think I should post the whole project since it also uses data from .txt files in order to log in. Would it be fine if I just paste the link to the project? – MrJ_ Jun 24 '21 at 11:24
  • .. including application, controller, fxml and complete stacktrace - and __NOT__ your complete project, but something __minimal__ just to demonstrate the same problem as in your project – kleopatra Jun 24 '21 at 11:25
  • No, no-one wants to see your whole project. *Create* a new project from scratch that does just enough to demonstrate the issue, and post that. It looks like all you need are a couple of very small FXML files (you already posted one), their controllers (which will be small, you already posted one of them), and the application class, which is just a few lines. Post the complete stack trace in the question. You can just mock the login, no need to make it check anything. – James_D Jun 24 '21 at 11:26
  • 1
    Alright, could you take a look now @James_D @kleopatra? I've made it as simple as I believe it can be and it still produces the same issue. – MrJ_ Jun 24 '21 at 11:41
  • I'm away from my computer, but does it still crash if you initialize peopleTableView when it is declared? – Jim D Jun 24 '21 at 11:52
  • 1
    @JimD That is an injected field. It is always an error to initialise a field annotated `@FXML`. – James_D Jun 24 '21 at 11:54
  • @JimD you mean if I initialize it as new TableView (despite it being declared in fxml file)? Well, in this case it doesn't crash anymore, but there is still no data in it, so something's wrong once again with setting the data. – MrJ_ Jun 24 '21 at 11:56
  • 1
    Yes, ignore the suggestion from @JimD. It is complete nonsense. I'm posting an answer. – James_D Jun 24 '21 at 11:57
  • 2
    By the way, the edits to the question are excellent. This is exactly how to create a [mre]. – James_D Jun 24 '21 at 12:06
  • @James_D I didn't recommend it as a solution rather as a troubleshooting step (hence it being a comment). Getting things to a running state and using the debugger can be useful. In this case it revealed that it was not being initialized by the FXML loader (the reason is documented in your solution). – Jim D Jun 24 '21 at 18:09
  • 2
    @JimD How would it help troubleshoot? You know from the stack trace it's null. You know it won't be null if you initialize it inline. That provides no information at all. The application already runs, so if you want to run it in the debugger, you can put a breakpoint in before you refer to the table and step through, if you need. – James_D Jun 24 '21 at 18:13
  • @James_D. If one forces the initialization and no longer get the NPE, that would support the hypothesis that something is wrong with the FXML file. As you point out in your solution, the code is using the same controller for two different FXML files (only one defines the peopleTableView). That should lead a programmer to ask "why is it being initialized in one FXML file and not the other." You have amazing insights from your experience and I learn a lot from your solutions. For some, the pile of reflection that is the FXML loader can be a source of confusion. – Jim D Jun 24 '21 at 21:03
  • 2
    @JimD But if it's annotated `@FXML` and it's null, you already know that the injection hasn't worked. If you initialize it, you know what's going to happen - it won't be null any more. You learn nothing from this. Your suggestion is illogical and will confuse people as to how things work. – James_D Jun 24 '21 at 21:42

1 Answers1

5

The problem is you're trying to use the same controller class for both FXML files (I think, assuming that this means you'll have the same controller instance both times, which is not the case).

The openNewWindow() method is called on the Controller instance that is created when you load sample.fxml. That FXML file has no field called peopleTableView, so peopleTableView is null in that instance, and peopleTableView.setItems(...) results in a null pointer exception.

The peopleTableView field is initialized in the Controller instance that is created when you load mainWindow.fxml, but no methods are called on that instance.

The solution is to use a different controller class for each FXML file (you should basically always do this). You can use the initialize() method in the controller for mainWindow.fxml to initialize the table data.

Here's the controller for sample.fxml:

public class LoginController {

    @FXML
    public void openNewWindow(){
        try {
            Parent root = FXMLLoader.load(getClass().getResource("mainWindow.fxml"));
            Stage mainWindow = new Stage();
            mainWindow.setTitle("App");
            mainWindow.setScene(new Scene(root, 1200, 600));
            mainWindow.initModality(Modality.APPLICATION_MODAL);
            mainWindow.show();

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

And here's the controller for mainWindow.fxml:

public class MainController {

    @FXML
    private TableView<Person> peopleTableView ;

    @FXML
    private void initialize() {
        ObservableList<Person> people = FXCollections.observableArrayList();

        people.add(new Person("Walter", "White"));
        people.add(new Person("Gus", "Fring"));


        peopleTableView.setItems(people);
    }
}

Now just update the fx:controller attributes in both FXML files.

If you need to pass data from one controller to the next, see Passing Parameters JavaFX FXML

For example, if you wanted to load the table data in the first controller's openNewWindow() method (which seems unnatural, but will serve as an example), you could do:

public class LoginController {

    @FXML
    public void openNewWindow(){
        try {

            // Note using FXMLLoader instance, not static load(URL) method:
            FXMLLoader loader = new FXMLLoader(getClass().getResource("mainWindow.fxml"));
            Parent root = loader.load();

            Stage mainWindow = new Stage();
            mainWindow.setTitle("App");
            mainWindow.setScene(new Scene(root, 1200, 600));
            mainWindow.initModality(Modality.APPLICATION_MODAL);
            mainWindow.show();

            ObservableList<Person> people = FXCollections.observableArrayList();

            people.add(new Person("Walter", "White"));
            people.add(new Person("Gus", "Fring"));

            MainController mainController = loader.getController();
            mainController.initializeTableData(people);

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

and

public class MainController {

    @FXML
    private TableView<Person> peopleTableView ;

    public void initializeTableData(ObservableList<Person> people) {

        peopleTableView.setItems(people);
    }
}

But again, for a full discussion of the various ways to communicate between controllers, see Passing Parameters JavaFX FXML

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks so much, it works absolutely fine. I actually find it weird, because in my project I am having two fxml files that belong to one Controller class and they are working just fine, but for some reason the third one with TableView can't cooperate. Anyway, I should probably add another controller to seperate those first two fxmls files just for good habits. – MrJ_ Jun 24 '21 at 12:33