i hope you can help me with a little project I want to do. I'm trying to create a video game using JavaFX for the GUI and I'm having some troubles. Here is the code I made for practice and I don't know why this don't work.
public class Main extends Application {
public GridPane map;
private int MAXH = 40;
private int MAXV = 40;
Random r = new Random();
@Override
public void start(Stage primaryStage) throws Exception{
map = new GridPane();
map.setGridLinesVisible(true);
map.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
//Get X position
int posX = (int) mouseEvent.getX() / 40;
//Get Y position
int posY = (int) mouseEvent.getY() / 40;
System.out.println("(" + posX + " " + posY + ")");
Canvas c = getCanvasNode(map, posX, posY);
}
});
for (int i = 0; i < MAXH; i++){
for(int j = 0; j < MAXV; j++){
//For each cell create a Canvas node
Canvas c = new Canvas(40, 40);
GraphicsContext gc = c.getGraphicsContext2D();
int k = r.nextInt();
//Randomly paint the Canvas
if (k % 3 == 0) gc.setFill(Color.BLUE);
else if (k % 2 == 0) gc.setFill(Color.RED);
else if (k % 7 == 0)gc.setFill(Color.GREEN);
//Filling the Canvas
gc.fillRect(0, 0, 40, 40);
//Adding to our Grid Pane
map.add(c, i, j);
}
}
primaryStage.setHeight(700);
primaryStage.setWidth(1200);
primaryStage.setScene(new Scene(map));
primaryStage.show();
}
private Canvas getCanvasNode(GridPane gridPane, int col, int row) {
for (Node node : gridPane.getChildren()) {
if (GridPane.getColumnIndex(node) == col && GridPane.getRowIndex(node) == row) {
return (Canvas) node;
}
}
return null;
}
public static void main(String[] args) {
launch(args);
}}
The problem becomes when I use the function "getCanvasNode" which produces a null pointer exception and i don't know why.
What i want to do is get the node in which i click-on through the map Event Handler can someone help me?
PD: I dont know if this is the most efficient/simple way to do this, if someone could give me some tips it would be awesome. I'm new in Java/JavaFX.
When you do
for (Node node : gridPane.getChildren()) { ... }
you iterate through all the child nodes of the grid pane, whether those are nodes you have added yourself, or nodes that are part of the internal implementation of the grid pane.
What is actually happening in your case is that, because you have called
map.setGridLinesVisible(true);
the grid pane has added a child node to represent the grid lines. This node doesn't have properties set for the columnIndex
or rowIndex
. So when the loop encounters that node, GridPane.getColumnIndex(node)
throws a NullPointerException
. (The implementation of getColumnIndex
looks in the node's property map for an Integer
object with a specific key, and then implicitly calls intValue()
on the result. So if no value is set, it attempts to call intValue()
on a null
reference.)
So you can fix this by doing something along the lines of
Integer columnIndex = GridPane.getColumnIndex(node);
and then treating the special case:
if (columnIndex != null && columnIndex.intValue() == col)
etc.
However, there is a far better approach to what you are trying to achieve. Instead of registering a listener with the pane, and then digging in to find out which child was clicked, just register listeners with the child nodes instead:
for (int i = 0; i < MAXH; i++){
for(int j = 0; j < MAXV; j++){
//For each cell create a Canvas node
Canvas c = new Canvas(40, 40);
GraphicsContext gc = c.getGraphicsContext2D();
int k = r.nextInt();
//Randomly paint the Canvas
if (k % 3 == 0) gc.setFill(Color.BLUE);
else if (k % 2 == 0) gc.setFill(Color.RED);
else if (k % 7 == 0)gc.setFill(Color.GREEN);
//Filling the Canvas
gc.fillRect(0, 0, 40, 40);
//Adding to our Grid Pane
map.add(c, i, j);
String message = "Click on cell ["+i+", "+j+"]";
c.setOnMouseClicked(e -> {
System.out.println(message);
// do anything else you need with the canvas....
});
}
}
The GridPane.getColumnIndex()
returns Integer
Java Object
, which wraps and serves as boxing for primitive int
Java data type. Primitive data type never can be null
, however Object
(so the Integer
) can be assigned to null
. If we read the java api documentation of GridPane.getColumnIndex()
it says,
Returns:
the column index for the child or null if no column index was
set
it can return null. The statement
if (GridPane.getColumnIndex(node) == col) {}
is implicitly broken down to
Integer colIndex = GridPane.getColumnIndex(node);
int colIndexVal = colIndex.intValue();
if (colIndexVal == col) {}
and if colIndex
is null, then colIndex.intValue()
throws the NullPointerException
. Which is happening in your code.
The solution is to take into account the null values:
if (GridPane.getColumnIndex(node) != null
&& GridPane.getColumnIndex(node) == col
&& GridPane.getRowIndex(node) != row
&& GridPane.getRowIndex(node) == row) {
return (Canvas) node;
}
The other approach can be changing the argument parameter type of col and row to Integer
private Canvas getCanvasNode( GridPane gridPane, Integer col, Integer row )
and use equals()
since the now we are comparing two Integer objects. This will take care of null values:
if ( col.equals( GridPane.getColumnIndex( node ) )
&& row.equals( GridPane.getRowIndex( node ) ) ) {
return ( Canvas ) node;
}
For more info please read on
- Data types in Java. Boxing and Unboxing
- Comparing Java primitive types and Objects
About the program itself, I think you are using too many Canvas
objects there. You may use JavaFX Rectangle
or Label
controls instead.
Accessing the child according to col & row values can be simplified as
Node node = gridPane.getChildren().get( col * MAXV + row + 1 );
return (node instanceof Canvas) ? ( Canvas ) node : null;