Search code examples
javajavafxnullpointerexceptionfxmlscenebuilder

JavaFx tableview RuntimeException


I am just trying to build a simple Gui with SceneBuilder and JavaFx however I can't figure out why I can't populate my TableView, it just stays empty even after inserting simple Testobjects. Here is the main Class and the Goal class.

package application;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ResourceBundle;

import Objects.Goal;
import UtilityClasses.GoalManager;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;



public class Main extends Application implements Initializable  {
    private Stage primaryStage;
    private AnchorPane mainLayout;
    public static ObservableList<Goal> goalsData = FXCollections.observableArrayList();

    @FXML
    static TableView<Goal> goalTable = new TableView<Goal>();
    @FXML
    static TableColumn<Goal, String> goalsColumn = new TableColumn<>();
    @FXML
    static TableColumn<Goal, String> statusColumn = new TableColumn<>();

    @Override
    public void start(Stage primaryStage) {


            this.primaryStage = primaryStage;
            this.primaryStage.setTitle("MainWindow");

            try {
                showMainView();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }











    }

    public static void main(String[] args) {

        try {
            setDefaultSettings();
            launch(args);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }



    }

    private void showMainView() throws IOException {
        SettingControlls mainController = new SettingControlls();
        FXMLLoader loader = new FXMLLoader();
        loader.setController(mainController);
        loader.setLocation(Main.class.getResource("mainFXML.fxml"));
        mainLayout = loader.load();
        Scene scene = new Scene(mainLayout);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private static void setDefaultSettings() throws IOException {

        File f = new File("Goals.txt");
        if(!f.exists()) {
            OutputStream os = new FileOutputStream("Goals.txt");
            Writer w = new OutputStreamWriter(os);
            w.close();
        }


    }
    private static ObservableList<Goal> getObservableList() throws FileNotFoundException, IOException {

        ObservableList<Goal> ol = FXCollections.observableArrayList(new Goal("testGoal"));
        return ol;

    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        goalsData.add(new Goal("test"));
        System.out.println(goalsData.get(0).getGoal());
        goalsColumn.setCellValueFactory(new PropertyValueFactory<Goal, String>("goal"));
        statusColumn.setCellValueFactory(new PropertyValueFactory<Goal, String>("status"));


        goalTable.setItems(goalsData);

    }
}

Here is the Goal class:

package Objects;

import javafx.beans.property.SimpleStringProperty;

public class Goal {


    private SimpleStringProperty goal;
    private SimpleStringProperty status;

    public Goal(String goal) {


        this.goal = new SimpleStringProperty(goal);
        this.status = new SimpleStringProperty("ongoing");
    }
    public Goal(String goal, String status) {


        this.goal = new SimpleStringProperty(goal);
        this.status = new SimpleStringProperty(status);
    }

    public String getGoal() {
        return this.goal.get();
    }
    public String getStatus() {
        return this.status.get();
    }
    public void setGoal(String newGoal) {

        this.goal = new SimpleStringProperty(newGoal);
    }
    public void setStatus(String newStatus) {

        this.status = new SimpleStringProperty(newStatus);
    }

}

Because the TableView and the columns get declared in my fxml file it seemed weird to me to generate them with new however if I don't do that I get an Nullpointerexception.

Edit: Added the fxml file:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<AnchorPane prefHeight="406.0" prefWidth="721.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <MenuBar layoutX="-7.0" layoutY="14.0" prefHeight="25.0" prefWidth="733.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <menus>
          <Menu mnemonicParsing="false" onAction="#GoalsClicked" style="-fx-font-size: 23;" text="Goals" />
          <Menu disable="true" mnemonicParsing="false" style="-fx-font-size: 23;" text="Matchups" />
        </menus>
      </MenuBar>
      <Text layoutX="14.0" layoutY="87.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Your current Goals:">
         <font>
            <Font size="24.0" />
         </font>
      </Text>
      <TableView fx:id="goalTable" layoutY="103.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0">
        <columns>
          <TableColumn fx:id="goalsColumn" maxWidth="1.7976931348623157E308" minWidth="0.0" prefWidth="625.0" text="Goals" />
          <TableColumn fx:id="statusColumn" maxWidth="1.7976931348623157E308" minWidth="0.0" prefWidth="72.0" text="Status" />
        </columns>
         <contextMenu>
            <ContextMenu>
              <items>
                <MenuItem mnemonicParsing="false" text="Delete" />
                  <MenuItem mnemonicParsing="false" text="Set status" />
              </items>
            </ContextMenu>
         </contextMenu>
      </TableView>
      <Button layoutX="619.0" layoutY="315.0" mnemonicParsing="false" onAction="#addGoal" text="+" />
      <Button layoutX="655.0" layoutY="315.0" mnemonicParsing="false" onAction="#removeGoal" text="-" />
   </children>
</AnchorPane>

Solution

  • You have many problems there :

    1. It is not recommended to use your Main class as a Controller class.
    2. Your Nodes used in .fxml are never static since they belong to the instance not to the class so remove them.
    3. Don't instantiate the Nodes defined in the .fxml file. There is nothing to do with new. The FXMLLoader does the work for you.

    So rewrite the following part:

        @FXML
        static TableView<Goal> goalTable = new TableView<Goal>();
        @FXML
        static TableColumn<Goal, String> goalsColumn = new TableColumn<>();
        @FXML
        static TableColumn<Goal, String> statusColumn = new TableColumn<>();
    

    I would also suggest splitting the Main class into a Main and a Controller class. In Main you should just load the file, and in the Controller do the UI related stuff. You can split the following way:

    Main:

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.scene.layout.AnchorPane;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml"));
            AnchorPane pane = loader.load();
            primaryStage.setScene(new Scene(pane, 400, 400));
            primaryStage.show();
        }
    }
    

    Controller:

    import javafx.fxml.Initializable;
    
    import java.net.URL;
    import java.util.ResourceBundle;
    
    public class Controller implements Initializable {
    
        @Override
        public void initialize(URL location, ResourceBundle resources) {
    
        }
    
    }
    

    and the .fxml

    <?import javafx.scene.layout.AnchorPane?>
    <AnchorPane xmlns="http://javafx.com/javafx"
                xmlns:fx="http://javafx.com/fxml"
                fx:controller="stackoverflow.dummy.Controller">
    
    </AnchorPane>
    

    Then you can complete these classes respecting those two rules I mentioned at 2. and 3.