I have created a program in Java and I am trying to create a UI for it with JavaFX. JavaFX comes with software called Scene Builder which allows you to edit an FXML file which is referenced by Java for creating the UI layout.

In Scene Builder I created a series of circle shapes. Here is a simplified version of the FXML file with just 4 circles:

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

<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.Circle?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <children>
      <Circle fx:id="nCircle_0_0" fill="DODGERBLUE" layoutX="100.0" layoutY="120.0" onMouseClicked="#btnClick" onMouseEntered="#circleHover" onMouseExited="#circleLeave" radius="20.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle fx:id="nCircle_0_1" fill="DODGERBLUE" layoutX="150.0" layoutY="120.0" onMouseClicked="#btnClick2" onMouseEntered="#circleHover" onMouseExited="#circleLeave" radius="20.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle fx:id="nCircle_0_2" fill="DODGERBLUE" layoutX="200.0" layoutY="120.0" onMouseEntered="#circleHover" onMouseExited="#circleLeave" radius="20.0" stroke="BLACK" strokeType="INSIDE" />
      <Circle fx:id="nCircle_0_3" fill="DODGERBLUE" layoutX="250.0" layoutY="120.0" onMouseEntered="#circleHover" onMouseExited="#circleLeave" radius="20.0" stroke="BLACK" strokeType="INSIDE" />
   </children>
</AnchorPane>

In order to control the appearance of these circle shapes I need to create variables referencing them in my Controller.Java file. I also put the variable names into a 2D array because I need to reference them from other 2D arrays in my Java program:

package sample;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

public class Controller {

    @FXML
    public Button btn;
    public Circle nCircle_0_0, nCircle_0_1, nCircle_0_2, nCircle_0_3;

    public Circle theCircles []  = {nCircle_0_0, nCircle_0_1, nCircle_0_2, nCircle_0_3};
    public Circle [][] circleGrid = new Circle[2][2];

    // get method for circleGrid
    public Circle [][] getcircleGrid(){
        return circleGrid;
    }

    public void makeCircleGrid(){
        int count = 0;
        for (int row = 0; row < 2; row++){
            for (int col = 0; col < 2; col++){
                //theCircles[count] = new Circle();
                circleGrid[row][col] = theCircles[count];
                count += 1;
            }
        }
    }

    protected void printCirclesGrid(Circle [][] theGrid){
        for (int row =0; row < 2; row++){
            System.out.print(row);
            for (int col = 0; col < 2; col++){
                System.out.print("\t" + theGrid [row][col]);
            }
            System.out.println();
        }

    }

    public void circleHover(MouseEvent event){
        Circle value = (Circle)event.getSource();
        value.setStrokeWidth(4);
    }

    public void circleLeave(MouseEvent event){
        Circle value = (Circle)event.getSource();
        value.setStrokeWidth(1);
    }

    @FXML
    public void btnClick(MouseEvent event)    {
        nCircle_0_3.setFill(Color.web("#ed4b00"));

    }

    @FXML
    public void btnClick2(MouseEvent event)    {
        circleGrid[1][0].setFill(Color.web("#ed4b00"));
    }

}

There is also a Main.java file that starts up JavaFX and loads in the FXML file:

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, 600, 600));
        primaryStage.show();

        Controller startUp = new Controller();
        startUp.makeCircleGrid();
        startUp.printCirclesGrid(startUp.getcircleGrid());
    }

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

The problem that I am having, is that I need to store the references to the circles in a 2D Array, because I need to reference and control them from elsewhere in my Java program. I tried putting all of the variable names into a 2D array, but Java sees them as null.

If you run the three files listed above, a window with 4 blue circles will appear. Clicking on the first circle will make the last circle turn red. However, clicking on the second circle will generate a java.lang.NullPointerException error. (If it had worked the way I was hoping, it would have turned the 2nd last circle red).

I was told that I need to initialise the Circle variable, by making it equal to a Circle object instance before placing it into the 2D array. This would get around the problem of null values in the 2D array. But I don't see how I can initialise the Circle values in the Controller.java file because the Circle objects were created in the FXML file and not in the Controller.java file. Is there some way I can store and reference objects that are created in the FXML file?

2 Answers

3
NeplatnyUdaj On

If you didn't omit any important part of information, it seems your variables are never initialized.

public Circle nCircle_0_0, nCircle_0_1, nCircle_0_2, nCircle_0_3, nCircle_0_4, nCircle_1_0, nCircle_1_1, nCircle_1_2, nCircle_1_3, nCircle_1_4;

This won't create any Circle, it just defines a variable which is initialized to null. You need to create an instance and assign it.

Until you do something like nCircle_0_0 = new Circle();, nCircle_0_0 will be null so you get NullPointerException.

But most importantly, in Java you work with references, not "variable names"(unless we're talking about reflection, which is not the case here). This means that when you assign a reference to the array and later assign another instance to your nCircle_0_0, the object in the array won't be affected. You should read(and understand) this: https://stackoverflow.com/a/40523/2266098

What is the reason you want to have defined variable for each element in the array? Why can't you just create the instances in your loop:

for (int row = 0; row < 5; row++){
    for (int col = 0; col < 5; col++){
        circleGrid[row][col] = new Circle();
    }
}

You won't be able to get the value through variable nCircle_0_0, but you use circleGrid[0][0] anyway.

0
Community On

I discovered what the problem is:

I had put the instructions into the Main.java file:

        Controller startUp = new Controller();
        startUp.makeCircleGrid();
        startUp.printCirclesGrid(startUp.getcircleGrid());

From what I can see this creates a different instance of the Controller class. It's not the same instance that is being referred to from the FXML file. As a result the values of nCircle_0_0 and nCircle_0_1 etc are seen as null when they go into the array.

If I instead trigger the method makeCircleGrid(); from one of the buttons or shapes created within my FXML file, then the full object reference goes into the array. So it sees the values of nCircle_0_0 and nCircle_0_1 etc as the actual Circle objects from the FXML file. It seems that the instance of the Controller class being talked to from within the FXML file, is different to the instance of the Controller class I attempted to talk to from Main.java